diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json
index 826564c4788..bf3ddad4bae 100644
--- a/client/i18n/locales/english/intro.json
+++ b/client/i18n/locales/english/intro.json
@@ -2866,7 +2866,13 @@
},
"cvs3": { "title": "229", "intro": [] },
"fgbp": { "title": "230", "intro": [] },
- "fgb1": { "title": "231", "intro": [] },
+ "workshop-todo-app": {
+ "title": "Build a Todo App using Local Storage",
+ "intro": [
+ "Local storage is a web browser feature that lets web applications store key-value pairs persistently within a user's browser. This allows web apps to save data during one session, then retrieve it in a later page session.",
+ "In this workshop, you'll learn how to handle form inputs, manage local storage, perform CRUD (Create, Read, Update, Delete) operations on tasks, implement event listeners, and toggle UI elements."
+ ]
+ },
"lab-bookmark-manager-app": {
"title": "Build a Bookmark Manager App",
"intro": ["For this lab, you will build a bookmark manager app."]
diff --git a/client/src/pages/learn/full-stack-developer/workshop-todo-app/index.md b/client/src/pages/learn/full-stack-developer/workshop-todo-app/index.md
new file mode 100644
index 00000000000..fe9eb755f18
--- /dev/null
+++ b/client/src/pages/learn/full-stack-developer/workshop-todo-app/index.md
@@ -0,0 +1,11 @@
+---
+title: Introduction to the Build a Todo App using Local Storage
+block: workshop-todo-app
+superBlock: full-stack-developer
+---
+
+## Introduction to the Build a Todo App using Local Storage
+
+Local storage is a web browser feature that lets web applications store key-value pairs persistently within a user's browser. This allows web apps to save data during one session, then retrieve it in a later page session.
+
+In this workshop, you'll learn how to handle form inputs, manage local storage, perform CRUD (Create, Read, Update, Delete) operations on tasks, implement event listeners, and toggle UI elements."
diff --git a/curriculum/challenges/_meta/workshop-todo-app/meta.json b/curriculum/challenges/_meta/workshop-todo-app/meta.json
new file mode 100644
index 00000000000..b97043a03f0
--- /dev/null
+++ b/curriculum/challenges/_meta/workshop-todo-app/meta.json
@@ -0,0 +1,285 @@
+{
+ "name": "Build a Todo App using Local Storage",
+ "isUpcomingChange": true,
+ "usesMultifileEditor": true,
+ "hasEditableBoundaries": true,
+ "dashedName": "workshop-todo-app",
+ "superBlock": "full-stack-developer",
+ "blockLayout": "challenge-grid",
+ "blockType": "workshop",
+ "challengeOrder": [
+ {
+ "id": "64e4e4c4ec263b62ae7bf54d",
+ "title": "Step 1"
+ },
+ {
+ "id": "64e4e6c86954de67a3e44ee3",
+ "title": "Step 2"
+ },
+ {
+ "id": "64e4e6fe78b5aa67ef2fc3e7",
+ "title": "Step 3"
+ },
+ {
+ "id": "64e4e7241f52bb682eeb8211",
+ "title": "Step 4"
+ },
+ {
+ "id": "64e4e74d0fb4f0687bf4145d",
+ "title": "Step 5"
+ },
+ {
+ "id": "64e4e78a7ea4a168de4e6a38",
+ "title": "Step 6"
+ },
+ {
+ "id": "64e4e7bbedb22d6939001ad3",
+ "title": "Step 7"
+ },
+ {
+ "id": "64e4eaaa9070a66aecbfe603",
+ "title": "Step 8"
+ },
+ {
+ "id": "64e4ebc7eabc5a6babd479cd",
+ "title": "Step 9"
+ },
+ {
+ "id": "64e4ecd7735a566c9266a338",
+ "title": "Step 10"
+ },
+ {
+ "id": "64e4eec13546c06d61a63d59",
+ "title": "Step 11"
+ },
+ {
+ "id": "660d86150a52ced178d567f3",
+ "title": "Step 12"
+ },
+ {
+ "id": "660d8ca387f989d6b25a3343",
+ "title": "Step 13"
+ },
+ {
+ "id": "660d8d7bb2424cd7cdf90ec1",
+ "title": "Step 14"
+ },
+ {
+ "id": "660d90b0ec1ef7da914c5e65",
+ "title": "Step 15"
+ },
+ {
+ "id": "660d936a55565add0a27199b",
+ "title": "Step 16"
+ },
+ {
+ "id": "660d9535e86fd2deb351aeb9",
+ "title": "Step 17"
+ },
+ {
+ "id": "650300a25b6f72964ab8aca6",
+ "title": "Step 18"
+ },
+ {
+ "id": "64ec89ee549ecf802de2b3e2",
+ "title": "Step 19"
+ },
+ {
+ "id": "660d9cb6cc6415e6ca0509d8",
+ "title": "Step 20"
+ },
+ {
+ "id": "64ec8f717b261e824d82d6a5",
+ "title": "Step 21"
+ },
+ {
+ "id": "64ec9145e424d8835a4e0f28",
+ "title": "Step 22"
+ },
+ {
+ "id": "64ec9282cd547785258cecf2",
+ "title": "Step 23"
+ },
+ {
+ "id": "64ec9343769e8f85c1e17e05",
+ "title": "Step 24"
+ },
+ {
+ "id": "64ec94f0de20c086e09b0fc3",
+ "title": "Step 25"
+ },
+ {
+ "id": "64ec959a76336c8767f5cd4d",
+ "title": "Step 26"
+ },
+ {
+ "id": "65099dbd8f137d58e5c0ff16",
+ "title": "Step 27"
+ },
+ {
+ "id": "64ec96761156a187ed32b274",
+ "title": "Step 28"
+ },
+ {
+ "id": "64ec9b10356c2d8aa05d9ce1",
+ "title": "Step 29"
+ },
+ {
+ "id": "64ec9c55fdeef78bacd2fc3b",
+ "title": "Step 30"
+ },
+ {
+ "id": "64fac365aeb8ad70b69b366f",
+ "title": "Step 31"
+ },
+ {
+ "id": "64fac4d1773e7a719b1254de",
+ "title": "Step 32"
+ },
+ {
+ "id": "64fac6a497811572b338e5e5",
+ "title": "Step 33"
+ },
+ {
+ "id": "64faca774fd9fd74bc084cc9",
+ "title": "Step 34"
+ },
+ {
+ "id": "64facf6180824876f70a2e86",
+ "title": "Step 35"
+ },
+ {
+ "id": "64fad07f43a101779cb8692a",
+ "title": "Step 36"
+ },
+ {
+ "id": "64fad9cd2eeb1e7ca2ca8c8b",
+ "title": "Step 37"
+ },
+ {
+ "id": "64fadae4f2d51b7d5d8b98d8",
+ "title": "Step 38"
+ },
+ {
+ "id": "64fadff23375f27ff06c6d40",
+ "title": "Step 39"
+ },
+ {
+ "id": "64faf0418e828c0114a558a7",
+ "title": "Step 40"
+ },
+ {
+ "id": "64fae068bcdc9c805bd8399e",
+ "title": "Step 41"
+ },
+ {
+ "id": "64faedcd16a1e985c4c2dc94",
+ "title": "Step 42"
+ },
+ {
+ "id": "64faf65b22ad8d07df9be14d",
+ "title": "Step 43"
+ },
+ {
+ "id": "64faf874364ec308f875f636",
+ "title": "Step 44"
+ },
+ {
+ "id": "64fafac95328110a69bcb75f",
+ "title": "Step 45"
+ },
+ {
+ "id": "64fb0fa0968f2b113b2d90e9",
+ "title": "Step 46"
+ },
+ {
+ "id": "64fb1061ca838611ed6a7d6b",
+ "title": "Step 47"
+ },
+ {
+ "id": "64fb1321e189a6136d200f77",
+ "title": "Step 48"
+ },
+ {
+ "id": "64fb1436adef3e145b4c3501",
+ "title": "Step 49"
+ },
+ {
+ "id": "64fb14d890415c14f93069ce",
+ "title": "Step 50"
+ },
+ {
+ "id": "64fb154a7c48cd159924bb18",
+ "title": "Step 51"
+ },
+ {
+ "id": "64fb1c4dc0feb219149a7c7d",
+ "title": "Step 52"
+ },
+ {
+ "id": "64fb285637fa1e1c222033e3",
+ "title": "Step 53"
+ },
+ {
+ "id": "64fb29348a60361ccd45c1e2",
+ "title": "Step 54"
+ },
+ {
+ "id": "64fefebad99209211ec30537",
+ "title": "Step 55"
+ },
+ {
+ "id": "64ff0313700dad264d19dfe4",
+ "title": "Step 56"
+ },
+ {
+ "id": "64ff04cc33779427a6412449",
+ "title": "Step 57"
+ },
+ {
+ "id": "64ff068e0426eb288874ed79",
+ "title": "Step 58"
+ },
+ {
+ "id": "64ff23daf176a92de95f24dc",
+ "title": "Step 59"
+ },
+ {
+ "id": "64ff24b80431f62ec6b93f65",
+ "title": "Step 60"
+ },
+ {
+ "id": "65003986d17d1e1865b269c0",
+ "title": "Step 61"
+ },
+ {
+ "id": "650046832f92c01a35834bca",
+ "title": "Step 62"
+ },
+ {
+ "id": "650048b0764f9c1b798200e2",
+ "title": "Step 63"
+ },
+ {
+ "id": "65004ba581d03d1d5628b41c",
+ "title": "Step 64"
+ },
+ {
+ "id": "6632420f81f3cc554a5e540b",
+ "title": "Step 65"
+ },
+ {
+ "id": "66ad0f178ed5791ed898fe56",
+ "title": "Step 66"
+ },
+ {
+ "id": "671682cd6d7aa95f0078f35f",
+ "title": "Step 67"
+ },
+ {
+ "id": "67168a7243b6396cb69c1bdf",
+ "title": "Step 68"
+ }
+ ],
+ "helpCategory": "JavaScript"
+}
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64e4e4c4ec263b62ae7bf54d.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64e4e4c4ec263b62ae7bf54d.md
new file mode 100644
index 00000000000..c8f15646343
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64e4e4c4ec263b62ae7bf54d.md
@@ -0,0 +1,311 @@
+---
+id: 64e4e4c4ec263b62ae7bf54d
+title: Step 1
+challengeType: 0
+dashedName: step-1
+demoType: onLoad
+---
+
+# --description--
+
+In this project, you will learn how `localStorage` works in JavaScript by building a Todo app. LocalStorage is a web storage feature of JavaScript that lets you persist data by storing the data as a **key:value** pair.
+
+The HTML and CSS for this project have been provided for you. Take a look at the files to get yourself familiarized with them.
+
+Begin by accessing the `task-form`, `confirm-close-dialog`, and `open-task-form-btn` elements with the `getElementById()` method. Save them in the variables `taskForm`, `confirmCloseDialog`, and `openTaskFormBtn`.
+
+# --hints--
+
+You should use `getElementById()` to access the `task-form` element.
+
+```js
+assert.match(code, /document\.getElementById\(\s*('|"|`)task\-form\1\s*\)\s*;?/)
+```
+
+You should assign the `task-form` element to the variable `taskForm`. Don't forget to use `const` to declare the variable.
+
+```js
+assert.match(code, /const\s+taskForm\s*=\s*document\.getElementById\(\s*('|"|`)task\-form\1\s*\)\s*;?/)
+```
+
+You should use `getElementById()` to access the `confirm-close-dialog` element.
+
+```js
+assert.match(code, /document\.getElementById\(\s*('|"|`)confirm\-close\-dialog\1\s*\)\s*;?/)
+```
+
+You should assign the `confirm-close-dialog` element to the variable `confirmCloseDialog`. Don't forget to use `const` to declare the variable.
+
+```js
+assert.match(code, /const\s+confirmCloseDialog\s*=\s*document\.getElementById\(\s*('|"|`)confirm\-close\-dialog\1\s*\)\s*;?/)
+```
+
+You should use `getElementById()` to access the `open-task-form-btn` element.
+
+```js
+assert.match(code, /document\.getElementById\(\s*('|"|`)open\-task\-form\-btn\1\s*\)\s*;?/)
+```
+
+You should assign the `open-task-form-btn` element to the variable `openTaskFormBtn`. Don't forget to use `const` to declare the variable.
+
+```js
+assert.match(code, /const\s+openTaskFormBtn\s*=\s*document\.getElementById\(\s*('|"|`)open\-task\-form\-btn\1\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+ \s*<\/div>/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+
Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(({id, title, date, description}) => {
+ tasksContainer.innerHTML += `
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+ `
+ }
+ );
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec94f0de20c086e09b0fc3.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec94f0de20c086e09b0fc3.md
new file mode 100644
index 00000000000..a829892ae48
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec94f0de20c086e09b0fc3.md
@@ -0,0 +1,349 @@
+---
+id: 64ec94f0de20c086e09b0fc3
+title: Step 25
+challengeType: 0
+dashedName: step-25
+---
+
+# --description--
+
+Create a `p` element and use template strings to set its content to the `title` you destructured. Right before the content of the `p` element, create a `strong` element with the text `Title:`.
+
+# --hints--
+
+You should create a `p` element.
+
+```js
+assert.match(code, /
/)
+```
+
+You should interpolate `${title}` as the text of your `p` element.
+
+```js
+assert.match(code, /
.*\$\{title\}<\/p>/)
+```
+
+You should create a `strong` element after the opening tag of your `p` element.
+
+```js
+assert.match(code, /
/)
+```
+
+Your `strong` element should have the text `Title:`.
+
+```js
+assert.match(code, /Title:\s*<\/strong>\s*/)
+```
+
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(({id, title, date, description}) => {
+ tasksContainer.innerHTML += `
+
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+
+ `
+ }
+ );
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec959a76336c8767f5cd4d.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec959a76336c8767f5cd4d.md
new file mode 100644
index 00000000000..12ed9bd71f0
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec959a76336c8767f5cd4d.md
@@ -0,0 +1,337 @@
+---
+id: 64ec959a76336c8767f5cd4d
+title: Step 26
+challengeType: 0
+dashedName: step-26
+---
+
+# --description--
+
+Similarly to the previous step, create another `p` element, and interpolate the `date` you destructured as the text content. Inside this paragraph, create a `strong` element with the text `Date:`.
+
+# --hints--
+
+You should create a `p` element and interpolate `${date}` as the text.
+
+```js
+assert.match(code, /.*\$\{date\}<\/p>/)
+```
+
+You should create a `strong` element with the text `Date:` after the opening tag of your `p` element.
+
+```js
+assert.match(code, /
Date:\s*<\/strong>\s*/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(({id, title, date, description}) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+
+ `
+ }
+ );
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec96761156a187ed32b274.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec96761156a187ed32b274.md
new file mode 100644
index 00000000000..ada798e204d
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec96761156a187ed32b274.md
@@ -0,0 +1,341 @@
+---
+id: 64ec96761156a187ed32b274
+title: Step 28
+challengeType: 0
+dashedName: step-28
+---
+
+# --description--
+
+To allow for task management, you need to include both a delete and an edit button for each task.
+
+Create two `button` elements with the `type` attribute set to `button` and the `class` attribute set to `btn`. Set the text of the first button to `Edit` and the text of the second button to `Delete`.
+
+# --hints--
+
+You should create a `button` element of type `button`, a class `btn` and `"Edit"` as the text, in that order.
+
+```js
+assert.match(code, /Edit<\/button/)
+```
+
+You should create a `button` element of type `button` a class `btn` and `Delete` as the text, in that order.
+
+```js
+assert.match(code, /Delete<\/button/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(({id, title, date, description}) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+
+ `
+ }
+ );
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec9b10356c2d8aa05d9ce1.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec9b10356c2d8aa05d9ce1.md
new file mode 100644
index 00000000000..1d6be341061
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec9b10356c2d8aa05d9ce1.md
@@ -0,0 +1,338 @@
+---
+id: 64ec9b10356c2d8aa05d9ce1
+title: Step 29
+challengeType: 0
+dashedName: step-29
+---
+
+# --description--
+
+After adding the task to the page, you should close the form modal to view the task. To do this, utilize `classList` to toggle the `hidden` class on the `taskForm` element.
+
+# --hints--
+
+You should use `classList` to toggle the class `hidden` on `taskForm`.
+
+```js
+const splitter = code.split("taskData.forEach")[1].split("});")
+assert.match(splitter[0], /taskForm\.classList\.toggle\(\s*('|")hidden\1\s*\)/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(({id, title, date, description}) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+});
+
+
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec9c55fdeef78bacd2fc3b.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec9c55fdeef78bacd2fc3b.md
new file mode 100644
index 00000000000..3c92db44d6b
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ec9c55fdeef78bacd2fc3b.md
@@ -0,0 +1,348 @@
+---
+id: 64ec9c55fdeef78bacd2fc3b
+title: Step 30
+challengeType: 0
+dashedName: step-30
+---
+
+# --description--
+
+If you attempt to add another task now, you'll notice that the input fields retain the values you entered for the previous task. To resolve this, you need to clear the input fields after adding a task.
+
+Instead of clearing the input fields one by one, it's a good practice to create a function that handles clearing those fields. You can then call this function whenever you need to clear the input fields again.
+
+Use arrow syntax to create a `reset` function and set it to a pair of curly braces.
+
+# --hints--
+
+You should use `const` and arrow syntax to create a `reset` function.
+
+```js
+assert.match(code, /const\s+reset\s*=\s*\(\s*\)\s*=>\s*\{\s*/)
+```
+
+Your `reset` function should be empty.
+
+```js
+assert.match(reset.toString(), /\(\s*\)\s*\{\s*\}/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(({id, title, date, description}) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+ taskForm.classList.toggle("hidden");
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac365aeb8ad70b69b366f.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac365aeb8ad70b69b366f.md
new file mode 100644
index 00000000000..e7cea7150ca
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac365aeb8ad70b69b366f.md
@@ -0,0 +1,367 @@
+---
+id: 64fac365aeb8ad70b69b366f
+title: Step 31
+challengeType: 0
+dashedName: step-31
+---
+
+# --description--
+
+Inside the `reset` function, set each value of `titleInput`, `dateInput`, `descriptionInput` to an empty string.
+
+Also, use `classList` to toggle the class `hidden` on the `taskForm` and set `currentTask` to an empty object. That's because at this point, `currentTask` will be filled with the task the user might have added.
+
+# --hints--
+
+You should set `titleInput.value` to an empty string.
+
+```js
+assert.match(reset.toString(), /titleInput\.value\s*=\s*('|")\1\s*;?/)
+```
+
+You should set `dateInput.value` to an empty string.
+
+```js
+assert.match(reset.toString(), /dateInput\.value\s*=\s*('|")\1\s*;?/)
+```
+
+You should set `descriptionInput.value` to an empty string.
+
+```js
+assert.match(reset.toString(), /descriptionInput\.value\s*=\s*('|")\1\s*;?/)
+```
+
+You should use `classList` to toggle the class `hidden` on `taskForm`
+
+```js
+assert.match(reset.toString(), /taskForm\.classList\.toggle\(\s*('|")hidden\1\s*\)/)
+```
+
+You should set `currentTask` to an empty object.
+
+```js
+assert.match(reset.toString(), /currentTask\s*=\s*\{\}\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+--fcc-editable-region--
+const reset = () => {
+
+}
+--fcc-editable-region--
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+ taskForm.classList.toggle("hidden");
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac4d1773e7a719b1254de.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac4d1773e7a719b1254de.md
new file mode 100644
index 00000000000..383d133c87c
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac4d1773e7a719b1254de.md
@@ -0,0 +1,352 @@
+---
+id: 64fac4d1773e7a719b1254de
+title: Step 32
+challengeType: 0
+dashedName: step-32
+---
+
+# --description--
+
+Remove the existing code toggling the class of `hidden` on `taskForm` and call the `reset` function instead. This would clear the input fields and also hide the form modal for the user to see the added task.
+
+# --hints--
+
+You should remove the code toggling the `hidden` class on `taskForm`.
+
+```js
+const splitter = code.split('Delete ')
+assert.notMatch(splitter[1], /taskForm\.classList\.toggle\(\s*("|')hidden\1\s*\)\s*;?/)
+```
+
+You should call the `reset` function.
+
+```js
+assert.match(code, /reset\(\s*\)/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+--fcc-editable-region--
+ taskForm.classList.toggle("hidden");
+--fcc-editable-region--
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac6a497811572b338e5e5.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac6a497811572b338e5e5.md
new file mode 100644
index 00000000000..12c838132f4
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fac6a497811572b338e5e5.md
@@ -0,0 +1,352 @@
+---
+id: 64fac6a497811572b338e5e5
+title: Step 33
+challengeType: 0
+dashedName: step-33
+---
+
+# --description--
+
+Also, remove the existing code toggling the class `hidden` on `taskForm` inside the `discardBtn` event listener and call the `reset` function instead. That's because when you click the `Discard` button, everything in the input fields should go away.
+
+# --hints--
+
+You should remove the code toggling the class `hidden` on `taskForm`.
+
+```js
+const splitter = code.split("confirmCloseDialog.close();")
+assert.notMatch(splitter[1], /taskForm\.classList\.toggle\(\s*("|')hidden\1\s*\)\s*;?/)
+```
+
+You should call the `reset` function.
+
+```js
+assert.match(code, /confirmCloseDialog\.close\(\s*\)\s*;?\s*reset\(\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+--fcc-editable-region--
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+--fcc-editable-region--
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+ reset()
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faca774fd9fd74bc084cc9.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faca774fd9fd74bc084cc9.md
new file mode 100644
index 00000000000..81ef21bbf0b
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faca774fd9fd74bc084cc9.md
@@ -0,0 +1,347 @@
+---
+id: 64faca774fd9fd74bc084cc9
+title: Step 34
+challengeType: 0
+dashedName: step-34
+---
+
+# --description--
+
+You should display the `Cancel` and `Discard` buttons to the user only if there is some text present in the input fields.
+
+To begin, within the `closeTaskFormBtn` event listener, create a `formInputsContainValues` variable to check if there is a value in the `titleInput` field **or** the `dateInput` field **or** the `descriptionInput` field.
+
+# --hints--
+
+You should use `const` to create a variable `formInputsContainValues` with the value `titleInput.value || dateInput.value || descriptionInput.value;`
+
+```js
+assert.match(code, /const\s+formInputsContainValues\s*=\s*(titleInput\.value\s*\|\|\s*dateInput\.value\s*\|\|\s*descriptionInput\.value|titleInput\.value\s*!==\s*null\s*\|\|\s*dateInput\.value\s*!==\s*null\s*\|\|\s*descriptionInput\.value\s*!==\s*null)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+--fcc-editable-region--
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+
+});
+--fcc-editable-region--
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+ reset()
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64facf6180824876f70a2e86.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64facf6180824876f70a2e86.md
new file mode 100644
index 00000000000..0fa6430e925
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64facf6180824876f70a2e86.md
@@ -0,0 +1,363 @@
+---
+id: 64facf6180824876f70a2e86
+title: Step 35
+challengeType: 0
+dashedName: step-35
+---
+
+# --description--
+
+Create an `if` statement to check if `formInputsContainValues` is true. If `formInputsContainValues` is true, indicating that there are changes, use the `showModal()` method on `confirmCloseDialog`. Otherwise, if there are no changes, call the `reset()` function to clear the input fields and hide the form modal.
+
+# --hints--
+
+You should create an `if` statement with the condition `formInputsContainValues`.
+
+```js
+assert.match(code, /if\s*\(\s*formInputsContainValues\s*\)\s*\{/)
+```
+
+The `if` block of your `if` statement should contain `confirmCloseDialog.showModal();`.
+
+```js
+assert.match(code, /if\s*\(\s*formInputsContainValues\s*\)\s*\{\s*confirmCloseDialog\.showModal\(\s*\)\s*;?/)
+```
+
+Your `if` statement should have an `else` block.
+
+```js
+assert.match(code, /if\s*\(\s*formInputsContainValues\s*\)\s*\{\s*confirmCloseDialog\.showModal\(\s*\)\s*;?\s*\}\s*else\s*\{/)
+```
+
+You should call the `reset()` function in the `else` block of your `if` statement.
+
+```js
+assert.match(code, /if\s*\(\s*formInputsContainValues\s*\)\s*\{\s*confirmCloseDialog\.showModal\(\s*\)\s*;?\s*\}\s*else\s*\{\s*reset\(\s*\)\s*;?\s*\}/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+--fcc-editable-region--
+ confirmCloseDialog.showModal();
+--fcc-editable-region--
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+ reset()
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fad07f43a101779cb8692a.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fad07f43a101779cb8692a.md
new file mode 100644
index 00000000000..7e0775f0236
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fad07f43a101779cb8692a.md
@@ -0,0 +1,373 @@
+---
+id: 64fad07f43a101779cb8692a
+title: Step 36
+challengeType: 0
+dashedName: step-36
+---
+
+# --description--
+
+You can enhance code readability and maintainability by refactoring the `submit` event listener into two separate functions. The first function can be used to add the input values to `taskData`, while the second function can be responsible for adding the tasks to the DOM.
+
+Use arrow syntax to create an `addOrUpdateTask` function. Then move the `dataArrIndex` variable, the `taskObj` object, and the `if` statement into the `addOrUpdateTask` function.
+
+# --hints--
+
+You should use `const` and arrow syntax to create an `addOrUpdateTask` function.
+
+```js
+assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\s*\)\s*=>\s*\{\s*/)
+```
+
+You should move the `dataArrIndex` variable into the `addOrUpdateTask` function.
+
+```js
+assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\s*(\(\s*item\s*\)|item)\s*=>\s*(item\.id\s*===\s*currentTask\.id|currentTask\.id\s*===\s*item\.id)\s*\)\s*;?/)
+```
+
+You should move the `taskObj` object into the `addOrUpdateTask` function.
+
+```js
+assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\s*(?:\(\s*item\s*\)|item)\s*=>\s*(?:item\.id\s*===\s*currentTask\.id|currentTask\.id\s*===\s*item\.id)\s*\)\s*;?\s*const\s+taskObj\s*=\s*\{\s*id\s*:\s*`\$\{\s*titleInput\.value\.toLowerCase\(\s*\)\.split\(\s*('|")\s{1}\1\s*\)\.join\(\s*('|")-\2\s*\)\s*\}-\$\{\s*Date\.now\(\s*\)\s*\}`\s*,\s*title\s*:\s*titleInput\.value\s*,\s*date\s*:\s*dateInput\.value\s*,\s*description\s*:\s*descriptionInput\.value\s*,?\s*\}\s*;?/)
+```
+
+You should move the `if` statement with the condition `dataArrIndex === -1` into your `addOrUpdateTask` function.
+
+```js
+assert.match(code, /const\s+addOrUpdateTask\s*=\s*\(\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\s*(?:\(\s*item\s*\)|item)\s*=>\s*(?:item\.id\s*===\s*currentTask\.id|currentTask\.id\s*===\s*item\.id)\s*\)\s*;?\s*const\s+taskObj\s*=\s*\{\s*id\s*:\s*`\$\{\s*titleInput\.value\.toLowerCase\(\s*\)\.split\(\s*('|")\s{1}\1\s*\)\.join\(\s*('|")-\2\s*\)\s*\}-\$\{\s*Date\.now\(\s*\)\s*\}`\s*,\s*title\s*:\s*titleInput\.value\s*,\s*date\s*:\s*dateInput\.value\s*,\s*description\s*:\s*descriptionInput\.value\s*,?\s*\}\s*;?\s*if\s*\(\s*dataArrIndex\s*===\s*-1\s*\)\s*\{\s*taskData\.unshift\(\s*taskObj\s*\)\s*;?\s*\}\s*\}\s*;?/)
+```
+
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+ reset()
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fad9cd2eeb1e7ca2ca8c8b.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fad9cd2eeb1e7ca2ca8c8b.md
new file mode 100644
index 00000000000..f17cd3df37d
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fad9cd2eeb1e7ca2ca8c8b.md
@@ -0,0 +1,359 @@
+---
+id: 64fad9cd2eeb1e7ca2ca8c8b
+title: Step 37
+challengeType: 0
+dashedName: step-37
+---
+
+# --description--
+
+Use arrow syntax to create an `updateTaskContainer` function. Then move the `taskData.forEach()` and its content into it.
+
+# --hints--
+
+You should use `const` and arrow syntax to create a `updateTaskContainer` function.
+
+```js
+assert.match(code, /const\s+updateTaskContainer\s*=\s*\(\s*\)\s*=>\s*\{/)
+```
+
+You should move `taskData.forEach()` and its content into the `updateTaskContainer()` function.
+
+```js
+assert.match(code, /const\s+updateTaskContainer\s*=\s*\(\s*\)\s*=>\s*\{\s*taskData\.forEach\(\s*\(\s*\{\s*id\s*,\s*title\s*,\s*date\s*,\s*description\s*\}\s*\)\s*=>\s*{\s*tasksContainer\.innerHTML\s*\+=\s*`\s*\s*
Title:<\/strong>\s*\$\{title\}<\/p>\s*Date:<\/strong>\s*\$\{date\}<\/p>\s*Description:<\/strong>\s*\$\{description\}<\/p>\s*Edit<\/button>\s*Delete<\/button>\s*<\/div>\s*`\s*}\s*\)\s*;?\s*\}\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+};
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+
+ reset()
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fadae4f2d51b7d5d8b98d8.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fadae4f2d51b7d5d8b98d8.md
new file mode 100644
index 00000000000..3f2532a5ce4
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fadae4f2d51b7d5d8b98d8.md
@@ -0,0 +1,361 @@
+---
+id: 64fadae4f2d51b7d5d8b98d8
+title: Step 38
+challengeType: 0
+dashedName: step-38
+---
+
+# --description--
+
+Inside the `addOrUpdateTask` function, call the `updateTaskContainer` and `reset` functions.
+
+# --hints--
+
+You should call the `updateTaskContainer` function.
+
+```js
+assert.match(code, /updateTaskContainer\(\s*\)\s*/)
+```
+
+You should call the `reset` function after calling the `updateTaskContainer` function.
+
+```js
+assert.match(code, /updateTaskContainer\(\s*\)\s*;?\s*reset\(\s*\)\s*;?\s*/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+};
+
+const updateTaskContainer = () => {
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ reset()
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fadff23375f27ff06c6d40.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fadff23375f27ff06c6d40.md
new file mode 100644
index 00000000000..d7f17450e24
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fadff23375f27ff06c6d40.md
@@ -0,0 +1,356 @@
+---
+id: 64fadff23375f27ff06c6d40
+title: Step 39
+challengeType: 0
+dashedName: step-39
+---
+
+# --description--
+
+Now remove the `reset()` call inside the `taskForm` submit event listener and call the `addOrUpdateTask` function instead.
+
+# --hints--
+
+You should remove `reset()` and call `addOrUpdateTask()`.
+
+```js
+assert.match(code, /addOrUpdateTask\(\s*\)/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ --fcc-editable-region--
+ reset()
+ --fcc-editable-region--
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fae068bcdc9c805bd8399e.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fae068bcdc9c805bd8399e.md
new file mode 100644
index 00000000000..d79c8abdad0
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fae068bcdc9c805bd8399e.md
@@ -0,0 +1,365 @@
+---
+id: 64fae068bcdc9c805bd8399e
+title: Step 41
+challengeType: 0
+dashedName: step-41
+---
+
+# --description--
+
+To enable editing and deleting for each task, add an `onclick` attribute to both buttons. Set the value of the `onclick` attribute to `editTask(this)` for the `Edit` button and `deleteTask(this)` for the `Delete` button. The `editTask(this)` function will handle editing, while the `deleteTask(this)` function will handle deletion.
+
+`this` is a keyword that refers to the current context. In this case, `this` points to the element that triggers the event – the buttons.
+
+# --hints--
+
+You should add `onclick="editTask(this)"` to your edit button.
+
+```js
+assert.match(code, /]*onclick\s*=\s*("|')editTask\(\s*this\s*\)\1)[^>]*>Edit<\/button>/);
+```
+
+You should add `onclick="deleteTask(this)"` to your delete button.
+
+```js
+assert.match(code, /]*onclick\s*=\s*("|')deleteTask\(\s*this\s*\)\1)[^>]*>Delete<\/button>/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+ --fcc-editable-region--
+
Edit
+
Delete
+ --fcc-editable-region--
+
+ `
+ }
+ );
+};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faedcd16a1e985c4c2dc94.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faedcd16a1e985c4c2dc94.md
new file mode 100644
index 00000000000..633f22f894e
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faedcd16a1e985c4c2dc94.md
@@ -0,0 +1,372 @@
+---
+id: 64faedcd16a1e985c4c2dc94
+title: Step 42
+challengeType: 0
+dashedName: step-42
+---
+
+# --description--
+
+Create a `deleteTask` function using arrow syntax. Pass `buttonEl` as the parameter and define an empty set of curly braces for the function body.
+
+# --hints--
+
+You should use `const` and arrow syntax to create a `deleteTask` function.
+
+```js
+assert.match(code, /const\s+deleteTask\s*=\s*(\(.*\)|[^\s()]+)\s*=>\s*/);
+```
+
+Your `deleteTask` function should take a `buttonEl` parameter.
+
+```js
+assert.match(deleteTask.toString(), /\(\s*buttonEl\s*\)/);
+```
+
+Your `deleteTask` function should be empty.
+
+```js
+assert.match(deleteTask.toString(), /\(\s*buttonEl\s*\)\s*\{\s*\}/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf0418e828c0114a558a7.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf0418e828c0114a558a7.md
new file mode 100644
index 00000000000..caca6956cd4
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf0418e828c0114a558a7.md
@@ -0,0 +1,360 @@
+---
+id: 64faf0418e828c0114a558a7
+title: Step 40
+challengeType: 0
+dashedName: step-40
+---
+
+# --description--
+
+There's a problem. If you add a task, and then add another, the previous task gets duplicated. This means you need to clear out the existing contents of `tasksContainer` before adding a new task.
+
+Set the `innerHTML` of `tasksContainer` back to an empty string.
+
+
+# --hints--
+
+You should set the `innerHTML` of `tasksContainer` to an empty string.
+
+```js
+assert.match(code, /tasksContainer\.innerHTML\s*=\s*("|')\1\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf65b22ad8d07df9be14d.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf65b22ad8d07df9be14d.md
new file mode 100644
index 00000000000..7e2bb13db06
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf65b22ad8d07df9be14d.md
@@ -0,0 +1,383 @@
+---
+id: 64faf65b22ad8d07df9be14d
+title: Step 43
+challengeType: 0
+dashedName: step-43
+---
+
+# --description--
+
+You need to find the index of the task you want to delete first.
+
+Create a `dataArrIndex` variable and set its value using the `findIndex()` method on the `taskData` array. Pass `item` as the parameter for the arrow callback function, and within the callback, check if the `id` of `item` is equal to the `id` of the `parentElement` of `buttonEl`.
+
+# --hints--
+
+You should not alter the `deleteTask` function.
+
+```js
+assert.match(code, /const\s+deleteTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*/)
+```
+
+You should use `const` to declare a `dataArrIndex` variable and set it to `taskData.findIndex()`.
+
+```js
+assert.match(code, /const\s+deleteTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(/)
+```
+
+You should pass in `item` as the parameter of the `findIndex()` arrow function callback.
+
+```js
+assert.match(code, /const\s+deleteTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\s*(\(\s*item\s*\)|item)/)
+```
+
+Your arrow function callback should check if `item.id === buttonEl.parentElement.id`. Don't use curly braces.
+
+```js
+assert.match(code, /const\s+deleteTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\s*(\(\s*item\s*\)|item)\s*=>\s*(item\.id\s*===\s*buttonEl\.parentElement\.id|buttonEl\.parentElement\.id\s*===\s*item\.id)\s*\)\s*;?\s*\}\s*;?/)
+```
+
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+--fcc-editable-region--
+const deleteTask = (buttonEl) => {
+
+}
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf874364ec308f875f636.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf874364ec308f875f636.md
new file mode 100644
index 00000000000..14b0a7850dc
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64faf874364ec308f875f636.md
@@ -0,0 +1,399 @@
+---
+id: 64faf874364ec308f875f636
+title: Step 44
+challengeType: 0
+dashedName: step-44
+---
+
+# --description--
+
+You need to remove the task from the DOM using `remove()` and from the `taskData` array using `splice()`.
+
+`splice()` is an array method that modifies arrays by removing, replacing, or adding elements at a specified index, while also returning the removed elements. It can take up to three arguments: the first one is the mandatory index at which to start, the second is the number of items to remove, and the third is an optional replacement element. Here's an example:
+
+```js
+const fruits = ["mango", "date", "cherry", "banana", "apple"];
+
+// Remove date and cherry from the array starting at index 1
+const removedFruits = fruits.splice(1, 2);
+
+console.log(fruits); // [ 'mango', 'banana', 'apple' ]
+console.log(removedFruits); // [ 'date', 'cherry' ]
+```
+
+Use the `remove()` method to remove the `parentElement` of the `buttonEl` from the DOM. Then use `splice()` to remove the task from the `taskData` array. Pass in `dataArrIndex` and `1` as the arguments of your `splice()`.
+
+`dataArrIndex` is the index to start and `1` is the number of items to remove.
+
+# --hints--
+
+You should use the `remove()` method to remove the parent element of `buttonEl`.
+
+```js
+assert.match(deleteTask.toString(), /buttonEl\.parentElement\.remove\(\s*\)\s*;?/)
+```
+
+You should use `splice()` on the `taskData` array.
+
+```js
+assert.match(deleteTask.toString(), /taskData\.splice\(/)
+```
+
+The first argument of your `splice()` method should be `dataArrIndex`.
+
+```js
+assert.match(deleteTask.toString(), /taskData\.splice\(\s*dataArrIndex/)
+```
+
+The second argument of your `splice()` method should be `1`.
+
+```js
+assert.match(deleteTask.toString(), /taskData\.splice\(\s*dataArrIndex\s*,\s*1\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+--fcc-editable-region--
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+}
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fafac95328110a69bcb75f.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fafac95328110a69bcb75f.md
new file mode 100644
index 00000000000..28e80549360
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fafac95328110a69bcb75f.md
@@ -0,0 +1,382 @@
+---
+id: 64fafac95328110a69bcb75f
+title: Step 45
+challengeType: 0
+dashedName: step-45
+---
+
+# --description--
+
+Use arrow syntax to create an `editTask` function. Pass in `buttonEl` as the parameter and add empty curly braces for the body.
+
+# --hints--
+
+You should use `const` and arrow syntax to create an `editTask` function.
+
+```js
+assert.match(code, /const\s+editTask\s*=\s*(\(.*\)|[^\s()]+)\s*=>\s*\{\s*/)
+```
+
+Your `editTask` function should take a `buttonEl` parameter.
+
+```js
+assert.match(editTask.toString(), /\(\s*buttonEl\s*\)/);
+```
+
+Your `editTask` function should be empty.
+
+```js
+assert.match(editTask.toString(), /\(\s*buttonEl\s*\)\s*\{\s*\}/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb0fa0968f2b113b2d90e9.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb0fa0968f2b113b2d90e9.md
new file mode 100644
index 00000000000..3d01d4eaec0
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb0fa0968f2b113b2d90e9.md
@@ -0,0 +1,392 @@
+---
+id: 64fb0fa0968f2b113b2d90e9
+title: Step 46
+challengeType: 0
+dashedName: step-46
+---
+
+# --description--
+
+As you did in the `deleteTask` function, you need to find the index of the task to be edited.
+
+Create a `dataArrIndex` variable. For its value, utilize the `findIndex()` method on `taskData`. Pass `item` as the parameter to its callback function and check if the `id` of `item` is equal to the `id` of the `parentElement` of `buttonEl`.
+
+# --hints--
+
+You should not alter the `editTask` function.
+
+```js
+assert.match(code, /const\s+editTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*/)
+```
+
+You should use `const` to declare a `dataArrIndex` variable and set it to `taskData.findIndex()`.
+
+```js
+assert.match(code, /const\s+editTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(/)
+```
+
+You should pass in `item` as the parameter of the `findIndex()` arrow function callback. Don't use curly braces.
+
+```js
+assert.match(code, /const\s+editTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\s*(\(\s*item\s*\)|item)/)
+```
+
+Your arrow function callback should check if `item.id === buttonEl.parentElement.id`.
+
+```js
+assert.match(code, /const\s+editTask\s*=\s*\(\s*buttonEl\s*\)\s*=>\s*\{\s*const\s+dataArrIndex\s*=\s*taskData\.findIndex\(\s*(\(\s*item\s*\)|item)\s*=>\s*(item\.id\s*===\s*buttonEl\.parentElement\.id|buttonEl\.parentElement\.id\s*===\s*item\.id)\s*\)\s*;?\s*\}\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+--fcc-editable-region--
+const editTask = (buttonEl) => {
+
+}
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1061ca838611ed6a7d6b.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1061ca838611ed6a7d6b.md
new file mode 100644
index 00000000000..8033fb285fd
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1061ca838611ed6a7d6b.md
@@ -0,0 +1,374 @@
+---
+id: 64fb1061ca838611ed6a7d6b
+title: Step 47
+challengeType: 0
+dashedName: step-47
+---
+
+# --description--
+
+Use square bracket notation to retrieve the task to be edited from the `taskData` array using the `dataArrIndex`. Then, assign it to the `currentTask` object to keep track of it.
+
+# --hints--
+
+You should assign `taskData[dataArrIndex]` to `currentTask`.
+
+```js
+assert.match(code, /currentTask\s*=\s*taskData\s*\[\s*dataArrIndex\s*\]\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+--fcc-editable-region--
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === buttonEl.parentElement.id);
+
+
+}
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1321e189a6136d200f77.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1321e189a6136d200f77.md
new file mode 100644
index 00000000000..43ef48dbee7
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1321e189a6136d200f77.md
@@ -0,0 +1,390 @@
+---
+id: 64fb1321e189a6136d200f77
+title: Step 48
+challengeType: 0
+dashedName: step-48
+---
+
+# --description--
+
+The task to be edited is now in the `currentTask` object. Stage it for editing inside the input fields by setting the value of `titleInput` to `currentTask.title`, `dateInput` to `currentTask.date`, and `descriptionInput` to `currentTask.description`.
+
+# --hints--
+
+You should set `titleInput.value` to `currentTask.title`.
+
+```js
+assert.match(editTask.toString(), /titleInput\.value\s*=\s*currentTask\.title\s*;?/)
+```
+
+You should set `dateInput.value` to `currentTask.date`.
+
+```js
+assert.match(editTask.toString(), /dateInput\.value\s*=\s*currentTask\.date\s*;?/)
+```
+
+You should set `descriptionInput.value` to `currentTask.description`.
+
+```js
+assert.match(editTask.toString(), /descriptionInput\.value\s*=\s*currentTask\.description\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+--fcc-editable-region--
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+
+}
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1436adef3e145b4c3501.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1436adef3e145b4c3501.md
new file mode 100644
index 00000000000..c25d526a84c
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1436adef3e145b4c3501.md
@@ -0,0 +1,381 @@
+---
+id: 64fb1436adef3e145b4c3501
+title: Step 49
+challengeType: 0
+dashedName: step-49
+---
+
+# --description--
+
+Set the `innerText` of the `addOrUpdateTaskBtn` button to `Update Task`.
+
+# --hints--
+
+You should set the inner text of the `addOrUpdateTaskBtn` button to `Update Task`
+
+```js
+assert.match(editTask.toString(), /addOrUpdateTaskBtn\.innerText\s*=\s*("|')Update Task\1\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+--fcc-editable-region--
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+}
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb14d890415c14f93069ce.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb14d890415c14f93069ce.md
new file mode 100644
index 00000000000..93810583ec5
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb14d890415c14f93069ce.md
@@ -0,0 +1,384 @@
+---
+id: 64fb14d890415c14f93069ce
+title: Step 50
+challengeType: 0
+dashedName: step-50
+---
+
+# --description--
+
+Finally, display the `form` modal with the values of the input fields by using `classList` to toggle the `hidden` class on `taskForm`.
+
+# --hints--
+
+You should use `classList` to toggle the class `hidden` on `taskForm`.
+
+```js
+assert.match(editTask.toString(), /taskForm\.classList\.toggle\(\s*('|")hidden\1\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+--fcc-editable-region--
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+
+}
+--fcc-editable-region--
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb154a7c48cd159924bb18.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb154a7c48cd159924bb18.md
new file mode 100644
index 00000000000..0331d2393ce
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb154a7c48cd159924bb18.md
@@ -0,0 +1,391 @@
+---
+id: 64fb154a7c48cd159924bb18
+title: Step 51
+challengeType: 0
+dashedName: step-51
+---
+
+# --description--
+
+At this point, editing a task won't reflect when you submit the task. To make the editing functional, go back to the `if` statement inside the `addOrUpdateTask` function. Create an `else` block and set `taskData[dataArrIndex]` to `taskObj`.
+
+# --hints--
+
+Your `if` statement should have an `else` block.
+
+```js
+assert.match(code, /if\s*\(\s*dataArrIndex\s*===\s*-1\s*\)\s*\{\s*taskData\.unshift\(\s*taskObj\s*\)\s*;?\s*\}\s*else\s*\{\s*/)
+```
+
+Your `else` block should have the code `taskData[dataArrIndex] = taskObj`.
+
+```js
+assert.match(code, /if\s*\(\s*dataArrIndex\s*===\s*-1\s*\)\s*\{\s*taskData\.unshift\(\s*taskObj\s*\)\s*;?\s*\}\s*else\s*\{\s*taskData\s*\[\s*dataArrIndex\s*\]\s*=\s*taskObj\s*;?\s*\}/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ --fcc-editable-region--
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ --fcc-editable-region--
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1c4dc0feb219149a7c7d.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1c4dc0feb219149a7c7d.md
new file mode 100644
index 00000000000..402505ebede
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb1c4dc0feb219149a7c7d.md
@@ -0,0 +1,408 @@
+---
+id: 64fb1c4dc0feb219149a7c7d
+title: Step 52
+challengeType: 0
+dashedName: step-52
+---
+
+# --description--
+
+If the user attempts to edit a task but decides not to make any changes before closing the form, there is no need to display the modal with the `Cancel` and `Discard` buttons.
+
+Inside the `closeTaskFormBtn` event listener, use `const` to create another variable named `formInputValuesUpdated`. Check if the user made changes while trying to edit a task by verifying that the `titleInput` value **is not equal to** `currentTask.title`, or the `dateInput` value **is not equal to** `currentTask.date`, or the `descriptionInput` value **is not equal to** `currentTask.description`.
+
+# --hints--
+
+Your `formInputValuesUpdated` variable should check if `titleInput.value` is not equal to `currentTask.title`.
+
+```js
+const regex = /const\s+formInputValuesUpdated\s*=(?=.*titleInput\.value\s*(!==|!=)\s*currentTask\.title)/
+
+assert.match(code, regex)
+```
+
+Your `formInputValuesUpdated` variable should check if `titleInput.value` is not equal to `currentTask.title` or `dateInput.value` is not equal to `currentTask.date`.
+
+```js
+const regex = /const\s+formInputValuesUpdated\s*=(?=.*titleInput\.value\s*(!==|!=)\s*currentTask\.title)(?=.*dateInput\.value\s*(?:!==|!=)\s*currentTask\.date)/
+
+assert.match(code, regex);
+
+```
+
+Your `formInputValuesUpdated` variable should check if `titleInput.value` is not equal to `currentTask.title`, `dateInput.value` is not equal to `currentTask.date`, or `descriptionInput.value` is not equal to `currentTask.description`.
+
+```js
+const regex = /const\s+formInputValuesUpdated\s*=\s*titleInput\.value\s*(!==|!=)\s*currentTask\.title\s*\|\|\s*dateInput\.value\s*(!==|!=)\s*currentTask\.date\s*\|\|\s*descriptionInput\.value\s*(!==|!=)\s*currentTask\.description\s*;?/
+
+assert.match(code, regex);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb285637fa1e1c222033e3.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb285637fa1e1c222033e3.md
new file mode 100644
index 00000000000..05e2e4c6d62
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb285637fa1e1c222033e3.md
@@ -0,0 +1,390 @@
+---
+id: 64fb285637fa1e1c222033e3
+title: Step 53
+challengeType: 0
+dashedName: step-53
+---
+
+# --description--
+
+Now add `formInputValuesUpdated` as the second mandatory condition in the `if` statement using the `AND` operator.
+
+This way, the `Cancel` and `Discard` buttons in the modal won't be displayed to the user if they haven't made any changes to the input fields while attempting to edit a task.
+
+# --hints--
+
+Your `if` should have the condition `formInputsContainValues && formInputValuesUpdated`.
+
+```js
+assert.match(code.split("if (formInputsContainValues"), /\s*&&\s*formInputValuesUpdated/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+--fcc-editable-region--
+ if (formInputsContainValues) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+--fcc-editable-region--
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb29348a60361ccd45c1e2.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb29348a60361ccd45c1e2.md
new file mode 100644
index 00000000000..8814dc3f799
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fb29348a60361ccd45c1e2.md
@@ -0,0 +1,414 @@
+---
+id: 64fb29348a60361ccd45c1e2
+title: Step 54
+challengeType: 0
+dashedName: step-54
+---
+
+# --description--
+
+`localStorage` offers methods for saving, retrieving, and deleting items. The items you save can be of any JavaScript data type.
+
+For instance, the `setItem()` method is used to save an item, and the `getItem()` method retrieves the item. To delete a specific item, you can utilize the `removeItem()` method, or if you want to delete all items in the storage, you can use `clear()`.
+
+Here's how you can save an item:
+
+```js
+localStorage.setItem("key", value); // value could be string, number, or any other data type
+```
+
+A `myTaskArr` array has been provided for you. Use the `setItem()` method to save it with a key of `data`.
+
+After that, open your browser console and go to the `Applications` tab, select `Local Storage`, and the freeCodeCamp domain you see.
+
+# --hints--
+
+Your `localStorage.setItem()` should have a key of `"data"`.
+
+```js
+assert.match(code, /localStorage\.setItem\(\s*("|')data\1/)
+```
+
+Your `localStorage.setItem()` should have a value of `myTaskArr`.
+
+```js
+assert.match(code, /localStorage\.setItem\(\s*("|')data\1\s*,\s*myTaskArr\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fefebad99209211ec30537.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fefebad99209211ec30537.md
new file mode 100644
index 00000000000..6b05c40279d
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64fefebad99209211ec30537.md
@@ -0,0 +1,404 @@
+---
+id: 64fefebad99209211ec30537
+title: Step 55
+challengeType: 0
+dashedName: step-55
+---
+
+# --description--
+
+If you check the "Application" tab of your browser console, you'll notice a series of `[object Object]`. This is because everything you save in `localStorage` needs to be in string format.
+
+To resolve the issue, wrap the data you're saving in the `JSON.stringify()` method. Then, check local storage again to observe the results.
+
+# --hints--
+
+Your `localStorage.setItem()` should have a key of `"data"`.
+
+```js
+assert.match(code, /localStorage\.setItem\(\s*("|')data\1/)
+```
+
+You should wrap `JSON.stringify()` around `myTaskArr`.
+
+```js
+assert.match(code, /localStorage\.setItem\(\s*("|')data\1\s*,\s*JSON\.stringify\(\s*myTaskArr\s*\)\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+--fcc-editable-region--
+localStorage.setItem("data", myTaskArr);
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff0313700dad264d19dfe4.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff0313700dad264d19dfe4.md
new file mode 100644
index 00000000000..578906c8407
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff0313700dad264d19dfe4.md
@@ -0,0 +1,406 @@
+---
+id: 64ff0313700dad264d19dfe4
+title: Step 56
+challengeType: 0
+dashedName: step-56
+---
+
+# --description--
+
+Now that you have the `myTaskArr` array saved in `localStorage` correctly, you can retrieve it with `getItem()` by specifying the key you used to save the item.
+
+Use the `getItem()` method to retrieve the `myTaskArr` array and assign it to the variable `getTaskArr`. Then, log the `getTaskArr` variable to the console to see the result.
+
+# --hints--
+
+You should use `const` to create a `getTaskArr` variable and assign `localStorage.getItem("data")` to it.
+
+```js
+assert.match(code, /const\s+getTaskArr\s*=\s*localStorage\.getItem\(\s*('|")data\1\s*\)\s*;?/)
+```
+
+You should log the `getTaskArr` variable to the console.
+
+```js
+assert.match(code, /console\.log\(\s*getTaskArr\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+localStorage.setItem("data", JSON.stringify(myTaskArr));
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff04cc33779427a6412449.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff04cc33779427a6412449.md
new file mode 100644
index 00000000000..24502aad096
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff04cc33779427a6412449.md
@@ -0,0 +1,411 @@
+---
+id: 64ff04cc33779427a6412449
+title: Step 57
+challengeType: 0
+dashedName: step-57
+---
+
+# --description--
+
+The item you retrieve is a string, as you saved it with `JSON.stringify()`. To view it in its original form before saving, you need to use `JSON.parse()`.
+
+Use `getItem()` to retrieve the `myTaskArr` array again. This time, wrap it inside `JSON.parse()`, assign it to the variable `getTaskArrObj` and log the `getTaskArrObj` to the console.
+
+Check the console to see the difference between `getTaskArr` and `getTaskObj`.
+
+# --hints--
+
+You should use `const` to create a `getTaskArrObj` variable and assign it to `JSON.parse(localStorage.getItem('data'));`.
+
+```js
+assert.match(code, /const\s+getTaskArrObj\s*=\s*JSON\.parse\(\s*localStorage\.getItem\(\s*('|")data\1\s*\)\s*\)\s*;?/)
+```
+
+You should log the `getTaskArrObj` variable to the console.
+
+```js
+assert.match(code, /console\.log\(\s*getTaskArrObj\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+localStorage.setItem("data", JSON.stringify(myTaskArr));
+
+const getTaskArr = localStorage.getItem("data")
+console.log(getTaskArr)
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff068e0426eb288874ed79.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff068e0426eb288874ed79.md
new file mode 100644
index 00000000000..ed5117c6192
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff068e0426eb288874ed79.md
@@ -0,0 +1,406 @@
+---
+id: 64ff068e0426eb288874ed79
+title: Step 58
+challengeType: 0
+dashedName: step-58
+---
+
+# --description--
+
+You can use `localStorage.removeItem()` to remove a specific item and `localStorage.clear()` to clear all items in the local storage.
+
+Remove the `data` item from local storage and open the console to observe the result. You should see `null`.
+
+# --hints--
+
+You should use `localStorage.removeItem()` to remove the `data` item from the browser's local storage.
+
+```js
+assert.match(code, /localStorage\.removeItem\(\s*('|")data\1\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+localStorage.setItem("data", JSON.stringify(myTaskArr));
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+const getTaskArr = localStorage.getItem("data")
+console.log(getTaskArr)
+
+const getTaskArrObj = JSON.parse(localStorage.getItem("data"));
+console.log(getTaskArrObj);
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff23daf176a92de95f24dc.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff23daf176a92de95f24dc.md
new file mode 100644
index 00000000000..9f98ac818c5
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff23daf176a92de95f24dc.md
@@ -0,0 +1,413 @@
+---
+id: 64ff23daf176a92de95f24dc
+title: Step 59
+challengeType: 0
+dashedName: step-59
+---
+
+# --description--
+
+Using `localStorage.clear()` won't just delete a single item from local storage but will remove all items.
+
+Remove `localStorage.removeItem()` and use `localStorage.clear()` instead. You don't need to pass in anything. You should also see `null` in the console.
+
+# --hints--
+
+You should remove `localStorage.removeItem("data")`.
+
+```js
+assert.notMatch(code, /localStorage\.removeItem\(\s*('|")data\1\s*\)\s*;?/)
+```
+
+You should remove everything from the browser `local storage` with `localStorage.clear()`.
+
+```js
+assert.match(code, /localStorage\.clear\(\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+localStorage.setItem("data", JSON.stringify(myTaskArr));
+
+--fcc-editable-region--
+localStorage.removeItem("data");
+
+--fcc-editable-region--
+
+const getTaskArr = localStorage.getItem("data")
+console.log(getTaskArr)
+
+const getTaskArrObj = JSON.parse(localStorage.getItem("data"));
+console.log(getTaskArrObj);
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff24b80431f62ec6b93f65.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff24b80431f62ec6b93f65.md
new file mode 100644
index 00000000000..fdcc43181e8
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/64ff24b80431f62ec6b93f65.md
@@ -0,0 +1,428 @@
+---
+id: 64ff24b80431f62ec6b93f65
+title: Step 60
+challengeType: 0
+dashedName: step-60
+---
+
+# --description--
+
+Remove the `myTaskArr` array and all of the code for `localStorage` because you don't need them anymore.
+
+# --hints--
+
+You should remove `myTaskArr` and all the code related to `localStorage` that you've just learned.
+
+```js
+assert.notMatch(code, /const\s+myTaskArr\s*=\s*\[\s*\{\s*task:\s*('|")Walk\s*the\s*Dog\1\s*,\s*date:\s*('|")22-04-2022\2\s*\}\s*,\s*\{\s*task:\s*('|")Read\s*some\s*books\3\s*,\s*date:\s*('|")02-11-2023\4\s*\}\s*,\s*\{\s*task:\s*('|")Watch\s*football\5\s*,\s*date:\s*('|")10-08-2021\6\s*\}\s*\]\s*;?\s*localStorage\.setItem\(('|")data\7\s*,\s*JSON\.stringify\(\s*myTaskArr\s*\)\s*;?\)\s*;?\s*localStorage\.clear\(\s*\)\s*;?\s*const\s+getTaskArr\s*=\s*localStorage\.getItem\(\s*('|")data\8\s*\)\s*console\.log\(\s*getTaskArr\s*\)\s*const\s+getTaskArrObj\s*=\s*JSON\.parse\(\s*localStorage\.getItem\(\s*('|")data\9\s*\)\s*\)\s*;?\s*console\.log\(\s*getTaskArrObj\s*\)\s*;?/);
+```
+
+You should remove any remaining references to `myTaskArr` anywhere in the code.
+
+```js
+assert.notMatch(code, /myTaskArr/);
+```
+
+You should remove any reference to `localStorage.getItem` for the item `"data"`.
+
+```js
+assert.notMatch(code, /localStorage\.getItem\(\s*('|")data\1\s*\)/);
+```
+
+You should remove any reference to `localStorage.clear()` from your code.
+
+```js
+assert.notMatch(code, /localStorage\.clear\(\s*\)\s*;?/);
+```
+
+You should remove any remaining references to `getTaskArrObj` anywhere in the code.
+
+```js
+assert.notMatch(code, /getTaskArrObj/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+
+--fcc-editable-region--
+const myTaskArr = [
+ { task: "Walk the Dog", date: "22-04-2022" },
+ { task: "Read some books", date: "02-11-2023" },
+ { task: "Watch football", date: "10-08-2021" },
+];
+
+localStorage.setItem("data", JSON.stringify(myTaskArr));
+
+localStorage.clear();
+
+const getTaskArr = localStorage.getItem("data")
+console.log(getTaskArr)
+
+const getTaskArrObj = JSON.parse(localStorage.getItem("data"));
+console.log(getTaskArrObj);
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65003986d17d1e1865b269c0.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65003986d17d1e1865b269c0.md
new file mode 100644
index 00000000000..e00ff428445
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65003986d17d1e1865b269c0.md
@@ -0,0 +1,404 @@
+---
+id: 65003986d17d1e1865b269c0
+title: Step 61
+challengeType: 0
+dashedName: step-61
+---
+
+# --description--
+
+Now you should save the task items to local storage when the user adds, updates, or removes a task.
+
+Inside the `addOrUpdateTask` function, use `setItem()` to save the tasks with a key of `data`, then pass the `taskData` array as its argument. Ensure that you stringify the `taskData`.
+
+This would persist data once the user adds or updates tasks.
+
+# --hints--
+
+You should use `localStorage.setItem()` to save the tasks to the browser `local storage`.
+
+```js
+assert.match(code, /localStorage\.setItem\(/)
+```
+
+You should pass in `"data"` as the first argument of your `localStorage.setItem()`.
+
+```js
+assert.match(code, /localStorage\.setItem\(\s*('|")data\1/)
+```
+
+You should pass in `JSON.stringify(taskData)` as the second argument of your `localStorage.setItem()`.
+
+```js
+assert.match(code, /localStorage\.setItem\(\s*('|")data\1\s*,\s*JSON\.stringify\(\s*taskData\s*\)\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650046832f92c01a35834bca.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650046832f92c01a35834bca.md
new file mode 100644
index 00000000000..64529f54b21
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650046832f92c01a35834bca.md
@@ -0,0 +1,407 @@
+---
+id: 650046832f92c01a35834bca
+title: Step 62
+challengeType: 0
+dashedName: step-62
+---
+
+# --description--
+
+You also want a deleted task to be removed from local storage. For this, you don't need the `removeItem()` or `clear()` methods. Since you already use `splice()` to remove the deleted task from `taskData`, all you need to do now is save `taskData` to local storage again.
+
+Use `setItem()` to save the `taskData` array again. Pass in `data` as the key and ensure that `taskData` is stringified before saving.
+
+# --hints--
+
+You should use `localStorage.setItem()` to save the tasks to the browser `local storage`.
+
+```js
+const splitter = code.split("taskData.splice(dataArrIndex, 1);")
+assert.match(splitter[1], /localStorage\.setItem\(/)
+```
+
+You should pass in `"data"` as the first argument of your `localStorage.setItem()`.
+
+```js
+const splitter = code.split("taskData.splice(dataArrIndex, 1);")
+assert.match(splitter[1], /localStorage\.setItem\(\s*('|")data\1/)
+```
+
+You should pass in `JSON.stringify(taskData)` as the second argument of your `localStorage.setItem()`.
+
+```js
+const splitter = code.split("taskData.splice(dataArrIndex, 1);")
+assert.match(splitter[1], /localStorage\.setItem\(\s*('|")data\1\s*,\s*JSON\.stringify\(\s*taskData\s*\)\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650048b0764f9c1b798200e2.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650048b0764f9c1b798200e2.md
new file mode 100644
index 00000000000..79a6c152d7f
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650048b0764f9c1b798200e2.md
@@ -0,0 +1,392 @@
+---
+id: 650048b0764f9c1b798200e2
+title: Step 63
+challengeType: 0
+dashedName: step-63
+---
+
+# --description--
+
+If you add, update, or remove a task, it should reflect in the UI. However, that's not happening now because you have yet to retrieve the tasks. To do this, you need to modify your initial `taskData` to be an empty array.
+
+Set `taskData` to the retrieval of `data` from local storage **or** an empty array. Make sure you parse the data coming with `JSON.parse()` because you saved it as a string.
+
+# --hints--
+
+You should set the `taskData` variable to `JSON.parse(localStorage.getItem("data")) || [];`.
+
+```js
+assert.match(code, /const\s+taskData\s*=\s*JSON\.parse\(\s*localStorage\.getItem\(\s*('|")data\1\s*\)\s*\)\s*\|\|\s*\[\s*\]\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+--fcc-editable-region--
+const taskData = [];
+--fcc-editable-region--
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ localStorage.setItem("data", JSON.stringify(taskData));
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65004ba581d03d1d5628b41c.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65004ba581d03d1d5628b41c.md
new file mode 100644
index 00000000000..8ffac9d6ccd
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65004ba581d03d1d5628b41c.md
@@ -0,0 +1,412 @@
+---
+id: 65004ba581d03d1d5628b41c
+title: Step 64
+challengeType: 0
+dashedName: step-64
+---
+
+# --description--
+
+You've retrieved the task item(s) now, but they still don't reflect in the UI when the page loads. However, they appear when you add a new task.
+
+You can check if there's a task inside `taskData` using the length of the array. Because `0` is a falsy value all you need for the condition is the array length.
+
+Here is an example checking the length of an array:
+
+```js
+if (arr.length) {
+ // do something
+}
+```
+
+In that example, if `arr` has a length greater than `0`, the code inside the `if` statement block will run. If `arr` has a length of `0`, the code inside the `if` statement block will not run.
+
+Check if there's a task inside `taskData`, then call the `updateTaskContainer()` inside the `if` statement block.
+
+# --hints--
+
+You should create an `if` statement with `(taskData.length)` as the condition. As a reminder, `0` is a falsy value.
+
+```js
+assert.match(code, /if\s*\(\s*taskData\.length\s*\)\s*\{\s*/)
+```
+
+You should call the `updateTaskContainer` function in your `if` statement.
+
+```js
+assert.match(code, /if\s*\(\s*taskData\.length\s*\)\s*\{\s*updateTaskContainer\(\s*\)\s*;?\s*\}/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = JSON.parse(localStorage.getItem("data")) || [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ localStorage.setItem("data", JSON.stringify(taskData));
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650300a25b6f72964ab8aca6.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650300a25b6f72964ab8aca6.md
new file mode 100644
index 00000000000..05d06ab9b7b
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/650300a25b6f72964ab8aca6.md
@@ -0,0 +1,323 @@
+---
+id: 650300a25b6f72964ab8aca6
+title: Step 18
+challengeType: 0
+dashedName: step-18
+---
+
+# --description--
+
+To make the `id` more unique, add another hyphen and use Date.now() .
+
+`Date.now()` returns the number of milliseconds elapsed since `January 1, 1970 00:00:00 UTC`.
+
+```js
+console.log(Date.now()); // 1628586800000
+```
+
+To see the new result, click on the `"Add New Task"` button. Then add a title of `WALK DOG` and click on the `"Add Task"` button. Open up the console to see the result.
+
+# --hints--
+
+You should add `-${Date.now()}` to `${titleInput.value.toLowerCase().split(' ').join('-')}`. Don't forget it needs to be inside the template string.
+
+```js
+assert.match(code, /\s*id:\s*`\$\{titleInput\.value\.toLowerCase\(\s*\)\.split\(\s*('|")\s{1}\1\s*\)\.join\(\s*('|")-\2\s*\)\}-\$\{Date\.now\(\s*\)\}`\s*/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ --fcc-editable-region--
+ id: `${titleInput.value.toLowerCase().split(' ').join('-')}`,
+ --fcc-editable-region--
+ };
+ console.log(taskObj);
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65099dbd8f137d58e5c0ff16.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65099dbd8f137d58e5c0ff16.md
new file mode 100644
index 00000000000..d9789dca335
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/65099dbd8f137d58e5c0ff16.md
@@ -0,0 +1,338 @@
+---
+id: 65099dbd8f137d58e5c0ff16
+title: Step 27
+challengeType: 0
+dashedName: step-27
+---
+
+# --description--
+
+Create one more `p` element and interpolate the `description` you destructured as the text. Also, create a `strong` element inside the paragraph with the text `Description:`.
+
+# --hints--
+
+You should create a `p` element with `${description}` as the text.
+
+```js
+assert.match(code, /.*\$\{description\}<\/p>/)
+```
+
+You should create a `strong` element with the text `Description:` after the opening tag of your `p` element.
+
+```js
+assert.match(code, /
Description:\s*<\/strong>\s*/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ }
+
+ taskData.forEach(({id, title, date, description}) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+
+ `
+ }
+ );
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d86150a52ced178d567f3.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d86150a52ced178d567f3.md
new file mode 100644
index 00000000000..2a6b890bf23
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d86150a52ced178d567f3.md
@@ -0,0 +1,330 @@
+---
+id: 660d86150a52ced178d567f3
+title: Step 12
+challengeType: 0
+dashedName: step-12
+---
+
+# --description--
+
+When a user creates a task, it should be saved in an object.
+
+Create a `const` variable called `taskObj` and assign it the value of an empty object.
+
+Then below that, add a console statement that logs the value of `taskObj` to the console.
+
+Open up the console to see the empty object.
+
+# --hints--
+
+You should use `const` to create your `taskObj` variable.
+
+```js
+assert.match(code, /const\s+taskObj\s*=\s*/);
+```
+
+You should assign an empty object to your `taskObj` variable.
+
+```js
+assert.match(code, /const\s+taskObj\s*=\s*{\s*}/);
+```
+
+You should use `console.log()` to log the value of `taskObj` to the console.
+
+```js
+assert.match(code, /console\.log\(\s*taskObj\s*\)/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d8ca387f989d6b25a3343.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d8ca387f989d6b25a3343.md
new file mode 100644
index 00000000000..b5693a9c5e5
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d8ca387f989d6b25a3343.md
@@ -0,0 +1,323 @@
+---
+id: 660d8ca387f989d6b25a3343
+title: Step 13
+challengeType: 0
+dashedName: step-13
+---
+
+# --description--
+
+Inside your `taskObj`, add an `id` property name. For the value use the value of the `titleInput`.
+
+To see the new result, click on the `"Add New Task"` button. Then add a title and click on the `"Add Task"` button. Open up the console to see the result.
+
+# --hints--
+
+Your `taskObj` should have an `id` property.
+
+```js
+assert.match(code, /const\s+taskObj\s*=\s*\{\s*("|'|)id\1/)
+```
+
+The `id` property of your `taskObj` object should be set to `titleInput.value`.
+
+```js
+assert.match(code, /const\s+taskObj\s*=\s*\{\s*("|'|)id\1\s*:\s*titleInput\.value\s*,?\s*\}/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+
+--fcc-editable-region--
+ const taskObj = {
+
+ };
+ console.log(taskObj);
+--fcc-editable-region--
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d8d7bb2424cd7cdf90ec1.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d8d7bb2424cd7cdf90ec1.md
new file mode 100644
index 00000000000..38e188a25ab
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d8d7bb2424cd7cdf90ec1.md
@@ -0,0 +1,323 @@
+---
+id: 660d8d7bb2424cd7cdf90ec1
+title: Step 14
+challengeType: 0
+dashedName: step-14
+---
+
+# --description--
+
+The user should be able to input a title with upper and lowercase letters. But for the `id`, the value should be all lowercase.
+
+Update your `titleInput.value` to be all lowercase. You can use the `toLowerCase()` method to do this.
+
+```js
+str.toLowerCase();
+```
+
+To see the new result, click on the `"Add New Task"` button. Then add a title of `WALK DOG` and click on the `"Add Task"` button. Open up the console to see the result of `"walk dog"`.
+
+# --hints--
+
+Your value should be `titleInput.value.toLowerCase()`.
+
+```js
+assert.match(code, /\s*id:\s*titleInput\.value\.toLowerCase\(\)/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+
+ const taskObj = {
+ --fcc-editable-region--
+ id: titleInput.value
+ --fcc-editable-region--
+ };
+ console.log(taskObj);
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d90b0ec1ef7da914c5e65.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d90b0ec1ef7da914c5e65.md
new file mode 100644
index 00000000000..ebe04b46f38
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d90b0ec1ef7da914c5e65.md
@@ -0,0 +1,319 @@
+---
+id: 660d90b0ec1ef7da914c5e65
+title: Step 15
+challengeType: 0
+dashedName: step-15
+---
+
+# --description--
+
+Right now, your `id` value is a lowercase string. But the final result should be a hyphenated string.
+
+Start by chaining the `split` method to the `titleInput.value` to split the string into an array of words. For the separator, use a space character(`" "`).
+
+To see the new result, click on the `"Add New Task"` button. Then add a title of `WALK DOG` and click on the `"Add Task"` button. Open up the console to see the result of `['walk', 'dog']`.
+
+# --hints--
+
+You should chain `split(" ")` to `titleInput.value.toLowerCase()`.
+
+```js
+assert.match(code, /\s*id:\s*titleInput\.value\.toLowerCase\(\s*\)\.split\(\s*('|")\s{1}\1\s*\)/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+
+ const taskObj = {
+ --fcc-editable-region--
+ id: titleInput.value.toLowerCase()
+ --fcc-editable-region--
+ };
+ console.log(taskObj);
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d936a55565add0a27199b.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d936a55565add0a27199b.md
new file mode 100644
index 00000000000..c1f4aa85947
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d936a55565add0a27199b.md
@@ -0,0 +1,317 @@
+---
+id: 660d936a55565add0a27199b
+title: Step 16
+challengeType: 0
+dashedName: step-16
+---
+
+# --description--
+
+Now that your `id` is an array of words, you can use the `join` method to turn the result back into a string. For the separator, use a hyphen(`-`).
+
+To see the new result, click on the `"Add New Task"` button. Then add a title of `WALK DOG` and click on the `"Add Task"` button. Open up the console to see the result of `"walk-dog"`.
+
+# --hints--
+
+You should chain `join("-")` to `titleInput.value.toLowerCase().split(" ")`.
+
+```js
+assert.match(code, /const\s+taskObj\s*=\s*\{\s*id:\s*titleInput\.value\.toLowerCase\(\s*\)\.split\(\s*('|")\s{1}\1\s*\)\.join\(\s*('|")-\2\s*\)\s*,?\s*\}/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+
+ const taskObj = {
+ --fcc-editable-region--
+ id: titleInput.value.toLowerCase().split(" ")
+ --fcc-editable-region--
+ };
+ console.log(taskObj);
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d9535e86fd2deb351aeb9.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d9535e86fd2deb351aeb9.md
new file mode 100644
index 00000000000..af2fffae584
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d9535e86fd2deb351aeb9.md
@@ -0,0 +1,325 @@
+---
+id: 660d9535e86fd2deb351aeb9
+title: Step 17
+challengeType: 0
+dashedName: step-17
+---
+
+# --description--
+
+There is one last thing you will need to do to make this `id` unique.
+
+But first, place the entire value below inside an embedded expression `${}`.
+
+```js
+titleInput.value.toLowerCase().split(" ").join("-")
+```
+
+Then wrap that value in template strings.
+
+In the next step, you will add a unique number to the end of the `id` value to make it truly unique.
+
+# --hints--
+
+Your value should be `${titleInput.value.toLowerCase().split(" ").join("-")}`. Don't forget to wrap the entire value in template strings.
+
+```js
+assert.match(code, /\s*id:\s*`\s*\${\s*titleInput\.value\.toLowerCase\(\s*\)\.split\(\s*('|")\s{1}\1\s*\)\.join\(\s*('|")-\2\s*\)\s*}`\s*/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+
+ const taskObj = {
+ --fcc-editable-region--
+ id: titleInput.value.toLowerCase().split(" ").join("-")
+ --fcc-editable-region--
+ };
+ console.log(taskObj);
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d9cb6cc6415e6ca0509d8.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d9cb6cc6415e6ca0509d8.md
new file mode 100644
index 00000000000..b64fd2f83e9
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/660d9cb6cc6415e6ca0509d8.md
@@ -0,0 +1,318 @@
+---
+id: 660d9cb6cc6415e6ca0509d8
+title: Step 20
+challengeType: 0
+dashedName: step-20
+---
+
+# --description--
+
+Now that have finished testing your `taskObj`, you can remove the `console.log(taskObj)` statement.
+
+# --hints--
+
+You should not have a `console.log(taskObj)` statement in your code.
+
+```js
+assert.notMatch(code, /console\.log\(\s*taskObj\s*\)/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = [];
+let currentTask = {};
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ confirmCloseDialog.showModal();
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ taskForm.classList.toggle("hidden");
+});
+
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+ --fcc-editable-region--
+ console.log(taskObj);
+ --fcc-editable-region--
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/6632420f81f3cc554a5e540b.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/6632420f81f3cc554a5e540b.md
new file mode 100644
index 00000000000..640a2c830d6
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/6632420f81f3cc554a5e540b.md
@@ -0,0 +1,397 @@
+---
+id: 6632420f81f3cc554a5e540b
+title: Step 65
+challengeType: 0
+dashedName: step-65
+---
+
+# --description--
+
+If you try to add a new task, edit that task, and then click on the `Add New Task` button, you will notice a bug.
+
+The form button will display the incorrect text of `"Update Task"` instead of `"Add Task"`. To fix this, you will need to assign the string `"Add Task"` to `addOrUpdateTaskBtn.innerText` inside your `reset` function.
+
+# --hints--
+
+You should assign the string `"Add Task"` to `addOrUpdateTaskBtn.innerText`.
+
+```js
+assert.match(code, /addOrUpdateTaskBtn\.innerText\s*=\s*('|")Add Task\1\s*/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = JSON.parse(localStorage.getItem("data")) || [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ localStorage.setItem("data", JSON.stringify(taskData));
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+if (taskData.length) {
+ updateTaskContainer();
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/66ad0f178ed5791ed898fe56.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/66ad0f178ed5791ed898fe56.md
new file mode 100644
index 00000000000..6d83efbb882
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/66ad0f178ed5791ed898fe56.md
@@ -0,0 +1,411 @@
+---
+id: 66ad0f178ed5791ed898fe56
+title: Step 66
+challengeType: 0
+dashedName: step-66
+---
+
+# --description--
+
+There are two bugs left to fix. First, if you create a task with only spaces for the title, an empty task will be created even though the title is required.
+
+Fix that by adding a check at the beginning of the function for if `!titleInput.value.trim()`. If there's no title, show an alert with the text `Please provide a title` and `return` after that.
+
+# --hints--
+
+You should add an `if` statement with the condition `!titleInput.value.trim()` inside `addOrUpdateTask` function.
+
+```js
+assert.match(code, /if\s*\(\s*!titleInput\.value\.trim\(\s*\)\s*\)\s*/)
+```
+
+You should call the `alert("Please provide a title")` in your `if` block.
+
+```js
+assert.match(code, /if\s*\(\s*!titleInput\.value\.trim\(\s*\)\s*\)\s*\{\s*alert\s*\(\s*('|"|`)Please provide a title\1\s*\)\s*/)
+```
+
+Your `if` statement should return from the function.
+
+```js
+assert.match(code, /if\s*\(\s*!titleInput\.value\.trim\(\s*\)\s*\)\s*\{\s*alert\s*\(\s*('|"|`)Please provide a title\1\s*\)\s*;?\s*return\s*;?\s*\}/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = JSON.parse(localStorage.getItem("data")) || [];
+let currentTask = {};
+
+const addOrUpdateTask = () => {
+ --fcc-editable-region--
+
+ --fcc-editable-region--
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ (tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `)
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ localStorage.setItem("data", JSON.stringify(taskData));
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ addOrUpdateTaskBtn.innerText = "Add Task";
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+if (taskData.length) {
+ updateTaskContainer();
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
+
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/671682cd6d7aa95f0078f35f.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/671682cd6d7aa95f0078f35f.md
new file mode 100644
index 00000000000..ebc8335b541
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/671682cd6d7aa95f0078f35f.md
@@ -0,0 +1,422 @@
+---
+id: 671682cd6d7aa95f0078f35f
+title: Step 67
+challengeType: 0
+dashedName: step-67
+---
+
+# --description--
+
+It is time to work on the final issue. If there is a to-do task with a special character like a quote inside of the name or description of the item, the application breaks. While it appears otherwise, the correct item will appear missing.
+
+In order to fix this, we need to create a function called `removeSpecialChars` that takes a string as input and removes all special characters.
+
+# --hints--
+
+You should define a `removeSpecialChars` function.
+
+```js
+assert.isFunction(removeSpecialChars);
+```
+
+Your `removeSpecialChars` should remove single quotes.
+
+```js
+assert.strictEqual(removeSpecialChars("quincy's dance party"),"quincys dance party");
+```
+
+Your `removeSpecialChars` should remove double quotes.
+
+```js
+assert.strictEqual(removeSpecialChars(`"Literature" report due`),"Literature report due");
+```
+
+Your `removeSpecialChars` should remove underscores.
+
+```js
+assert.strictEqual(removeSpecialChars(`Naomi has a __gif__ folder`),"Naomi has a gif folder");
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = JSON.parse(localStorage.getItem("data")) || [];
+let currentTask = {};
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+const addOrUpdateTask = () => {
+ if(!titleInput.value.trim()){
+ alert("Please provide a title");
+ return;
+ }
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ (tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `)
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ localStorage.setItem("data", JSON.stringify(taskData));
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ addOrUpdateTaskBtn.innerText = "Add Task";
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+if (taskData.length) {
+ updateTaskContainer();
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
+
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-todo-app/67168a7243b6396cb69c1bdf.md b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/67168a7243b6396cb69c1bdf.md
new file mode 100644
index 00000000000..31efe8f81d3
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-todo-app/67168a7243b6396cb69c1bdf.md
@@ -0,0 +1,803 @@
+---
+id: 67168a7243b6396cb69c1bdf
+title: Step 68
+challengeType: 0
+dashedName: step-68
+---
+
+# --description--
+
+Finally, it is time to call the `removeSpecialChars` on the `id`, `title`, and `description` properties in your `taskObj`.
+
+This will remove issues with broken task data.
+
+With that you have completed the project.
+
+# --hints--
+
+You should call `removeSpecialChars` on `titleInput.value` when assigning the `id`.
+
+```js
+assert.match(code, /\s*id:\s*`\$\{removeSpecialChars\(titleInput\.value\)\.toLowerCase\(\s*\)\.split\(\s*('|")\s{1}\1\s*\)\.join\(\s*('|")-\2\s*\)\}-\$\{Date\.now\(\s*\)\}`\s*/)
+```
+
+You should call `removeSpecialChars` on `titleInput.value` when assigning the `title`.
+
+```js
+assert.match(code, /\s*title:\s*removeSpecialChars\(titleInput\.value\)\s*/,)
+```
+
+You should call `removeSpecialChars` on `descriptionInput.value` when assigning the `description`.
+
+```js
+assert.match(code, /\s*description:\s*removeSpecialChars\(descriptionInput\.value\)\s*/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = JSON.parse(localStorage.getItem("data")) || [];
+let currentTask = {};
+
+const removeSpecialChars = (val) => {
+ return val.trim().replace(/[^A-Za-z0-9\-\s]/g, '')
+}
+
+const addOrUpdateTask = () => {
+ if(!titleInput.value.trim()){
+ alert("Please provide a title");
+ return;
+ }
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ --fcc-editable-region--
+ const taskObj = {
+ id: `${titleInput.value.toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title: titleInput.value,
+ date: dateInput.value,
+ description: descriptionInput.value,
+ };
+ --fcc-editable-region--
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ (tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `)
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ localStorage.setItem("data", JSON.stringify(taskData));
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ addOrUpdateTaskBtn.innerText = "Add Task";
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+if (taskData.length) {
+ updateTaskContainer();
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
+
+
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+ Learn localStorage by Building a Todo App
+
+
+
+
+
+ Todo App
+
+
+ Add New Task
+
+
+
+
+ Title
+
+ Date
+
+ Description
+
+
+
+
+
+
+ Discard unsaved changes?
+
+
+ Cancel
+
+
+ Discard
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --white: #fff;
+ --light-grey: #f5f6f7;
+ --dark-grey: #0a0a23;
+ --yellow: #f1be32;
+ --golden-yellow: #feac32;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--dark-grey);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+h1 {
+ color: var(--light-grey);
+ margin: 20px 0 40px 0;
+}
+
+.todo-app {
+ background-color: var(--white);
+ width: 300px;
+ height: 350px;
+ border: 5px solid var(--yellow);
+ border-radius: 8px;
+ padding: 15px;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn {
+ cursor: pointer;
+ width: 100px;
+ margin: 10px;
+ color: var(--dark-grey);
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+
+.btn:hover {
+ background-image: linear-gradient(#ffcc4c, #f89808);
+}
+
+.large-btn {
+ width: 80%;
+ font-size: 1.2rem;
+ align-self: center;
+ justify-self: center;
+}
+
+.close-task-form-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+
+.close-icon {
+ width: 20px;
+ height: 20px;
+}
+
+.task-form {
+ display: flex;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--white);
+ border-radius: 5px;
+ padding: 15px;
+ width: 300px;
+ height: 350px;
+ flex-direction: column;
+ justify-content: space-between;
+ overflow: auto;
+}
+
+.task-form-header {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.task-form-body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.task-form-footer {
+ display: flex;
+ justify-content: center;
+}
+
+.task-form-label,
+#title-input,
+#date-input,
+#description-input {
+ display: block;
+}
+
+.task-form-label {
+ margin-bottom: 5px;
+ font-size: 1.3rem;
+ font-weight: bold;
+}
+
+#title-input,
+#date-input,
+#description-input {
+ width: 100%;
+ margin-bottom: 10px;
+ padding: 2px;
+}
+
+#confirm-close-dialog {
+ padding: 10px;
+ margin: 10px auto;
+ border-radius: 15px;
+}
+
+.confirm-close-dialog-btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.discard-message-text {
+ font-weight: bold;
+ font-size: 1.5rem;
+}
+
+#tasks-container {
+ height: 100%;
+ overflow-y: auto;
+}
+
+.task {
+ margin: 5px 0;
+}
+
+.hidden {
+ display: none;
+}
+
+@media (min-width: 576px) {
+ .todo-app,
+ .task-form {
+ width: 400px;
+ height: 450px;
+ }
+
+ .task-form-label {
+ font-size: 1.5rem;
+ }
+
+ #title-input,
+ #date-input {
+ height: 2rem;
+ }
+
+ #title-input,
+ #date-input,
+ #description-input {
+ padding: 5px;
+ margin-bottom: 20px;
+ }
+}
+```
+
+```js
+const taskForm = document.getElementById("task-form");
+const confirmCloseDialog = document.getElementById("confirm-close-dialog");
+const openTaskFormBtn = document.getElementById("open-task-form-btn");
+const closeTaskFormBtn = document.getElementById("close-task-form-btn");
+const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
+const cancelBtn = document.getElementById("cancel-btn");
+const discardBtn = document.getElementById("discard-btn");
+const tasksContainer = document.getElementById("tasks-container");
+const titleInput = document.getElementById("title-input");
+const dateInput = document.getElementById("date-input");
+const descriptionInput = document.getElementById("description-input");
+
+const taskData = JSON.parse(localStorage.getItem("data")) || [];
+let currentTask = {};
+
+const removeSpecialChars = (val) => {
+ return val.trim().replace(/[^A-Za-z0-9\-\s]/g, '')
+}
+
+const addOrUpdateTask = () => {
+ if(!titleInput.value.trim()){
+ alert("Please provide a title");
+ return;
+ }
+ const dataArrIndex = taskData.findIndex((item) => item.id === currentTask.id);
+ const taskObj = {
+ id: `${removeSpecialChars(titleInput.value).toLowerCase().split(" ").join("-")}-${Date.now()}`,
+ title:removeSpecialChars(titleInput.value) ,
+ date: dateInput.value,
+ description: removeSpecialChars(descriptionInput.value),
+ };
+
+ if (dataArrIndex === -1) {
+ taskData.unshift(taskObj);
+ } else {
+ taskData[dataArrIndex] = taskObj;
+ }
+
+ localStorage.setItem("data", JSON.stringify(taskData));
+ updateTaskContainer()
+ reset()
+};
+
+const updateTaskContainer = () => {
+ tasksContainer.innerHTML = "";
+
+ taskData.forEach(
+ ({ id, title, date, description }) => {
+ (tasksContainer.innerHTML += `
+
+
Title: ${title}
+
Date: ${date}
+
Description: ${description}
+
Edit
+
Delete
+
+ `)
+ }
+ );
+};
+
+
+const deleteTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ buttonEl.parentElement.remove();
+ taskData.splice(dataArrIndex, 1);
+ localStorage.setItem("data", JSON.stringify(taskData));
+}
+
+const editTask = (buttonEl) => {
+ const dataArrIndex = taskData.findIndex(
+ (item) => item.id === buttonEl.parentElement.id
+ );
+
+ currentTask = taskData[dataArrIndex];
+
+ titleInput.value = currentTask.title;
+ dateInput.value = currentTask.date;
+ descriptionInput.value = currentTask.description;
+
+ addOrUpdateTaskBtn.innerText = "Update Task";
+
+
+ taskForm.classList.toggle("hidden");
+}
+
+const reset = () => {
+ addOrUpdateTaskBtn.innerText = "Add Task";
+ titleInput.value = "";
+ dateInput.value = "";
+ descriptionInput.value = "";
+ taskForm.classList.toggle("hidden");
+ currentTask = {};
+}
+
+if (taskData.length) {
+ updateTaskContainer();
+}
+
+openTaskFormBtn.addEventListener("click", () =>
+ taskForm.classList.toggle("hidden")
+);
+
+closeTaskFormBtn.addEventListener("click", () => {
+ const formInputsContainValues = titleInput.value || dateInput.value || descriptionInput.value;
+ const formInputValuesUpdated = titleInput.value !== currentTask.title || dateInput.value !== currentTask.date || descriptionInput.value !== currentTask.description;
+
+ if (formInputsContainValues && formInputValuesUpdated) {
+ confirmCloseDialog.showModal();
+ } else {
+ reset();
+ }
+});
+
+cancelBtn.addEventListener("click", () => confirmCloseDialog.close());
+
+discardBtn.addEventListener("click", () => {
+ confirmCloseDialog.close();
+ reset()
+});
+
+taskForm.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ addOrUpdateTask();
+});
+```
diff --git a/curriculum/superblock-structure/full-stack.json b/curriculum/superblock-structure/full-stack.json
index a06e4ca56c8..c6a17cb3a87 100644
--- a/curriculum/superblock-structure/full-stack.json
+++ b/curriculum/superblock-structure/full-stack.json
@@ -408,6 +408,7 @@
{
"dashedName": "localstorage-and-crud-operations",
"blocks": [
+ { "dashedName": "workshop-todo-app" },
{ "dashedName": "lab-bookmark-manager-app" },
{ "dashedName": "review-local-storage-and-crud" },
{ "dashedName": "quiz-local-storage-and-crud" }
diff --git a/curriculum/test/utils/mongo-ids.js b/curriculum/test/utils/mongo-ids.js
index 7a3262fe7b4..dc7885b0b7f 100644
--- a/curriculum/test/utils/mongo-ids.js
+++ b/curriculum/test/utils/mongo-ids.js
@@ -1013,6 +1013,77 @@ const duplicatedProjectIds = [
/*** JavaScript ***/
+ // Local Storage ToDo App
+
+ '64e4e4c4ec263b62ae7bf54d',
+ '64e4e6c86954de67a3e44ee3',
+ '64e4e6fe78b5aa67ef2fc3e7',
+ '64e4e7241f52bb682eeb8211',
+ '64e4e74d0fb4f0687bf4145d',
+ '64e4e78a7ea4a168de4e6a38',
+ '64e4e7bbedb22d6939001ad3',
+ '64e4eaaa9070a66aecbfe603',
+ '64e4ebc7eabc5a6babd479cd',
+ '64e4ecd7735a566c9266a338',
+ '64e4eec13546c06d61a63d59',
+ '660d86150a52ced178d567f3',
+ '660d8ca387f989d6b25a3343',
+ '660d8d7bb2424cd7cdf90ec1',
+ '660d90b0ec1ef7da914c5e65',
+ '660d936a55565add0a27199b',
+ '660d9535e86fd2deb351aeb9',
+ '650300a25b6f72964ab8aca6',
+ '64ec89ee549ecf802de2b3e2',
+ '660d9cb6cc6415e6ca0509d8',
+ '64ec8f717b261e824d82d6a5',
+ '64ec9145e424d8835a4e0f28',
+ '64ec9282cd547785258cecf2',
+ '64ec9343769e8f85c1e17e05',
+ '64ec94f0de20c086e09b0fc3',
+ '64ec959a76336c8767f5cd4d',
+ '65099dbd8f137d58e5c0ff16',
+ '64ec96761156a187ed32b274',
+ '64ec9b10356c2d8aa05d9ce1',
+ '64ec9c55fdeef78bacd2fc3b',
+ '64fac365aeb8ad70b69b366f',
+ '64fac4d1773e7a719b1254de',
+ '64fac6a497811572b338e5e5',
+ '64faca774fd9fd74bc084cc9',
+ '64facf6180824876f70a2e86',
+ '64fad07f43a101779cb8692a',
+ '64fad9cd2eeb1e7ca2ca8c8b',
+ '64fadae4f2d51b7d5d8b98d8',
+ '64fadff23375f27ff06c6d40',
+ '64faf0418e828c0114a558a7',
+ '64fae068bcdc9c805bd8399e',
+ '64faedcd16a1e985c4c2dc94',
+ '64faf65b22ad8d07df9be14d',
+ '64faf874364ec308f875f636',
+ '64fafac95328110a69bcb75f',
+ '64fb0fa0968f2b113b2d90e9',
+ '64fb1061ca838611ed6a7d6b',
+ '64fb1321e189a6136d200f77',
+ '64fb1436adef3e145b4c3501',
+ '64fb14d890415c14f93069ce',
+ '64fb154a7c48cd159924bb18',
+ '64fb1c4dc0feb219149a7c7d',
+ '64fb285637fa1e1c222033e3',
+ '64fb29348a60361ccd45c1e2',
+ '64fefebad99209211ec30537',
+ '64ff0313700dad264d19dfe4',
+ '64ff04cc33779427a6412449',
+ '64ff068e0426eb288874ed79',
+ '64ff23daf176a92de95f24dc',
+ '64ff24b80431f62ec6b93f65',
+ '65003986d17d1e1865b269c0',
+ '650046832f92c01a35834bca',
+ '650048b0764f9c1b798200e2',
+ '65004ba581d03d1d5628b41c',
+ '6632420f81f3cc554a5e540b',
+ '66ad0f178ed5791ed898fe56',
+ '671682cd6d7aa95f0078f35f',
+ '67168a7243b6396cb69c1bdf',
+
// Rock, Paper, Scissors Game
// Palindrome Checker