diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json
index 1b947300be9..0a6cb23a122 100644
--- a/client/i18n/locales/english/intro.json
+++ b/client/i18n/locales/english/intro.json
@@ -3042,7 +3042,12 @@
"You'll also practice basic regular expressions, template literals, the addEventListener() method, and more."
]
},
- "egkv": { "title": "213", "intro": [] },
+ "lab-customer-complaint-form": {
+ "title": "Build a Customer Complaint Form",
+ "intro": [
+ "For this lab, you will use JavaScript to validate a customer complaint form."
+ ]
+ },
"review-form-validation-with-javascript": {
"title": "Form Validation with JavaScript Review",
"intro": [
diff --git a/client/src/pages/learn/full-stack-developer/lab-customer-complaint-form/index.md b/client/src/pages/learn/full-stack-developer/lab-customer-complaint-form/index.md
new file mode 100644
index 00000000000..77bde14d11f
--- /dev/null
+++ b/client/src/pages/learn/full-stack-developer/lab-customer-complaint-form/index.md
@@ -0,0 +1,9 @@
+---
+title: Introduction to the Build a Customer Complaint Form
+block: lab-customer-complaint-form
+superBlock: full-stack-developer
+---
+
+## Introduction to the Build a Customer Complaint Form
+
+For this lab, you will use JavaScript to validate a customer complaint form.
diff --git a/curriculum/challenges/_meta/lab-customer-complaint-form/meta.json b/curriculum/challenges/_meta/lab-customer-complaint-form/meta.json
new file mode 100644
index 00000000000..13ee027c19a
--- /dev/null
+++ b/curriculum/challenges/_meta/lab-customer-complaint-form/meta.json
@@ -0,0 +1,11 @@
+{
+ "name": "Build a Customer Complaint Form",
+ "isUpcomingChange": true,
+ "usesMultifileEditor": true,
+ "blockType": "lab",
+ "blockLayout": "link",
+ "dashedName": "lab-customer-complaint-form",
+ "superBlock": "full-stack-developer",
+ "challengeOrder": [{ "id": "67279fe50237291f80eed8b8", "title": "Build a Customer Complaint Form" }],
+ "helpCategory": "JavaScript"
+}
diff --git a/curriculum/challenges/english/25-front-end-development/lab-customer-complaint-form/67279fe50237291f80eed8b8.md b/curriculum/challenges/english/25-front-end-development/lab-customer-complaint-form/67279fe50237291f80eed8b8.md
new file mode 100644
index 00000000000..78f13743ed4
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/lab-customer-complaint-form/67279fe50237291f80eed8b8.md
@@ -0,0 +1,857 @@
+---
+id: 67279fe50237291f80eed8b8
+title: Build a Customer Complaint Form
+challengeType: 25
+dashedName: build-a-customer-complaint-form
+demoType: onClick
+---
+
+# --description--
+
+For this lab, you have been provided with all the HTML and CSS. You will use JavaScript to validate the complaint form.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. When the form is submitted, you should ensure that:
+ - `#full-name` is not empty.
+ - `#email` is a valid email address format.
+ - `#order-no` is a sequence of ten numbers starting with `2024`.
+ - `#product-code` follows the pattern `XX##-X###-XX#`, where `X` represents either a lowercase letter or an uppercase letter and `#` represents a number.
+ - `#quantity` is a positive integer.
+ - at least one checkbox from `#complaints-group` is checked.
+ - `#complaint-description` contains at least twenty characters if the `Other` checkbox is checked.
+ - a radio button from `#solutions-group` is selected.
+ - `#solution-description` contains at least twenty characters if the `Other` radio button is selected.
+1. You should have a function named `validateForm` that returns an object containing the following keys: `full-name`, `email`, `order-no`, `product-code`, `quantity`, `complaints-group`, `complaint-description`, `solutions-group`, and `solution-description`. The value of each key should be `true` if the corresponding form field is correctly filled and `false` otherwise.
+1. You should have a function named `isValid` that takes the object returned by `validateForm` as argument and returns `true` if every form field is correctly filled and `false` otherwise.
+1. If a change event is triggered on a form field and it has a valid value, you should set its border color to `green`. In case of checkbox and radio button groups, you should set the border color of the parent `fieldset`.
+1. If a change event is triggered on a form field and it has an invalid value, you should set its border color to `red`. In case of checkbox and radio button groups, you should set the border color of the parent `fieldset`.
+1. When you try to submit the form you should call `isValid` to validate the form.
+1. When you try to submit the form, if the form has any invalid field, each invalid field should be highlighted by setting the border color of each invalid input, textarea or fieldset (in case of checkbox and radio button groups) to `red`.
+
+# --hints--
+
+You should have a function named `validateForm`.
+
+```js
+assert.isFunction(validateForm);
+```
+
+`validateForm` should return an object.
+
+```js
+assert.isObject(validateForm());
+```
+
+`validateForm()["full-name"]` should be `false` when `#full-name` is empty, and `true` otherwise.
+
+```js
+const field = document.getElementById("full-name");
+field.value = "";
+assert.isFalse(validateForm()["full-name"]);
+field.value = "testing";
+assert.isTrue(validateForm()["full-name"]);
+```
+
+When a `change` event is triggered on `#full-name`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
+
+```js
+const field = document.getElementById("full-name");
+field.value = "testing"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "green");
+field.value = ""
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "red");
+```
+
+`validateForm()["email"]` should be `true` when `#email` contains a valid email address, and `false` otherwise.
+
+```js
+const field = document.getElementById("email");
+field.value = "example@domain.com"
+assert.isTrue(validateForm()["email"]);
+field.value = "testing"
+assert.isFalse(validateForm()["email"]);
+```
+
+When a `change` event is triggered on `#email`, you should set its border color to `green` if it contains a valid email address, and `red` otherwise.
+
+```js
+const field = document.getElementById("email");
+field.value = "example@domain.com"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "green");
+field.value = ""
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "red");
+```
+
+`validateForm()["order-no"]` should be `true` when `#order-no` contains a valid value, and `false` otherwise.
+
+```js
+const field = document.getElementById("order-no");
+field.value = "2024001122"
+assert.isTrue(validateForm()["order-no"]);
+field.value = "testing"
+assert.isFalse(validateForm()["order-no"]);
+```
+
+When a `change` event is triggered on `#order-no`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
+
+```js
+const field = document.getElementById("order-no");
+field.value = "2024001122"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "green");
+field.value = "20240011"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "red");
+```
+
+`validateForm()["product-code"]` should be `true` when `#product-code` contains a valid value, and `false` otherwise.
+
+```js
+const field = document.getElementById("product-code");
+field.value = "Xa22-X123-Xb4";
+assert.isTrue(validateForm()["product-code"]);
+field.value = "testing"
+assert.isFalse(validateForm()["product-code"]);
+```
+
+When a `change` event is triggered on `#product-code`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
+
+```js
+const field = document.getElementById("product-code");
+field.value = "Xa22-X123-Xb4"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "green");
+field.value = "Xa22-123-Xb4"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "red");
+```
+
+`validateForm()["quantity"]` should be `true` when `#quantity` contains a valid value, and `false` otherwise.
+
+```js
+const field = document.getElementById("quantity");
+field.value = "5";
+assert.isTrue(validateForm()["quantity"]);
+field.value = "0";
+assert.isFalse(validateForm()["quantity"]);
+```
+
+When a `change` event is triggered on `#quantity`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
+
+```js
+const field = document.getElementById("quantity");
+field.value = "2"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "green");
+field.value = "0"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "red");
+```
+
+When at least one checkbox from `#complaints-group` is checked, `validateForm()["complaints-group"]` should be `true`.
+
+```js
+document.getElementById("damaged-product").checked = true
+assert.isTrue(validateForm()["complaints-group"]);
+```
+
+When none of the checkboxes from `#complaints-group` is checked, `validateForm()["complaints-group"]` should be `false`.
+
+```js
+document.getElementById("damaged-product").checked = false;
+document.getElementById("nonconforming-product").checked = false;
+document.getElementById("delayed-dispatch").checked = false;
+document.getElementById("other-complaint").checked = false;
+assert.isFalse(validateForm()["complaints-group"]);
+```
+
+Once one checkbox from `#complaints-group` is checked, you should set `#complaints-group`'s border color to `green`.
+
+```js
+document.getElementById("damaged-product").checked = true;
+document.getElementById("nonconforming-product").checked = false;
+document.getElementById("delayed-dispatch").checked = false;
+document.getElementById("other-complaint").checked = false;
+const fieldset = document.getElementById("complaints-group");
+fieldset.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(fieldset.style.borderColor, "green");
+```
+
+When all of the checkboxes from `#complaints-group` are changed to the unchecked state, you should set `#complaints-group`'s border color to `red`.
+
+```js
+document.getElementById("damaged-product").checked = false;
+document.getElementById("nonconforming-product").checked = false;
+document.getElementById("delayed-dispatch").checked = false;
+document.getElementById("other-complaint").checked = false;
+const fieldset = document.getElementById("complaints-group");
+fieldset.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(fieldset.style.borderColor, "red");
+```
+
+When `#other-complaint` is checked and `#complaint-description` contains at least twenty characters, `validateForm()["complaint-description"]` should be `true`.
+
+```js
+document.getElementById("other-complaint").checked = true
+document.getElementById("complaint-description").value = "A sentence with at least twenty characters";
+assert.isTrue(validateForm()["complaint-description"]);
+```
+
+When `#other-complaint` is checked and `#complaint-description` contains less than twenty characters, `validateForm()["complaint-description"]` should be `false`.
+
+```js
+document.getElementById("other-complaint").checked = true
+document.getElementById("complaint-description").value = "Less than 20 chars";
+assert.isFalse(validateForm()["complaint-description"]);
+```
+
+Once the value of `#complaint-description` is changed to a valid value, you should set its border color to `green`.
+
+```js
+const field = document.getElementById("complaint-description");
+field.value = "A sentence with at least twenty characters"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "green");
+```
+
+Once the value of `#complaint-description` is changed to an invalid value, you should set its border color to `red`.
+
+```js
+const field = document.getElementById("complaint-description");
+field.value = "Not enough chars"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "red");
+```
+
+When a radio button from `#solutions-group` is checked, `validateForm()["solutions-group"]` should be `true`.
+
+```js
+document.getElementById("refund").checked = true
+assert.isTrue(validateForm()["solutions-group"]);
+```
+
+When none of the radio buttons from `#solutions-group` is checked, `validateForm()["solutions-group"]` should be `false`.
+
+```js
+document.getElementById("refund").checked = false;
+document.getElementById("exchange").checked = false;
+document.getElementById("other-solution").checked = false;
+assert.isFalse(validateForm()["solutions-group"]);
+```
+
+Once a radio button from `#solutions-group` is checked, you should set `#solutions-group`'s border color to `green`.
+
+```js
+document.getElementById("refund").checked = true;
+document.getElementById("exchange").checked = false;
+document.getElementById("other-solution").checked = false;
+const fieldset = document.getElementById("solutions-group");
+fieldset.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(fieldset.style.borderColor, "green");
+```
+
+When all of the checkboxes from `#complaints-group` are changed to the unchecked state, you should set `#complaints-group`'s border color to `red`.
+
+```js
+document.getElementById("refund").checked = false;
+document.getElementById("exchange").checked = false;
+document.getElementById("other-solution").checked = false;
+const fieldset = document.getElementById("solutions-group");
+fieldset.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(fieldset.style.borderColor, "red");
+```
+
+When `#other-solution` is checked and `#solution-description` contains at least twenty characters, `validateForm()["solution-description"]` should be `true`.
+
+```js
+document.getElementById("other-solution").checked = true
+document.getElementById("solution-description").value = "A sentence with at least twenty characters";
+assert.isTrue(validateForm()["solution-description"]);
+```
+
+When `#other-solution` is checked and `#solution-description` contains less than twenty characters, `validateForm()["solution-description"]` should be `false`.
+
+```js
+document.getElementById("other-solution").checked = true
+document.getElementById("solution-description").value = "Less than 20 chars";
+assert.isFalse(validateForm()["solution-description"]);
+```
+
+Once the value of `#solution-description` is changed to a valid value, you should set its border color to `green`.
+
+```js
+const field = document.getElementById("solution-description");
+field.value = "A sentence with at least twenty characters"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "green");
+```
+
+Once the value of `#solution-description` is changed to an invalid value, you should set its border color to `red`.
+
+```js
+const field = document.getElementById("solution-description");
+field.value = "Not enough chars"
+field.dispatchEvent(new Event("change", { bubbles: true }));
+assert.equal(field.style.borderColor, "red");
+```
+
+You should have a function named `isValid`.
+
+```js
+assert.isFunction(isValid);
+```
+
+Your `isValid` function should take the `validateForm()` as its argument and return `true` when all the form fields have been filled correctly.
+
+```js
+document.getElementById("full-name").value = "testing"
+document.getElementById("email").value = "example@domain.com"
+document.getElementById("order-no").value = "2024001122"
+document.getElementById("product-code").value = "Xa22-X123-Xb4"
+document.getElementById("quantity").value = "5"
+document.getElementById("damaged-product").checked = true
+document.getElementById("other-complaint").checked = false
+document.getElementById("refund").checked = true
+document.getElementById("other-solution").checked = false
+assert.isTrue(isValid(validateForm()));
+
+document.getElementById("other-complaint").checked = true
+document.getElementById("complaint-description").value = "A sentence with at least twenty characters";
+document.getElementById("other-solution").checked = true
+document.getElementById("solution-description").value = "A sentence with at least twenty characters";
+assert.isTrue(isValid(validateForm()));
+```
+
+Your `isValid` function should take the `validateForm()` as its argument and return `false` when not all the form fields have been filled correctly.
+
+```js
+const name = document.getElementById("full-name");
+const email = document.getElementById("email");
+const orderNo = document.getElementById("order-no");
+const prCode = document.getElementById("product-code");
+const quantity = document.getElementById("quantity");
+const damaged = document.getElementById("damaged-product");
+const nonConforming = document.getElementById("nonconforming-product");
+const delayed = document.getElementById("delayed-dispatch");
+const otherC = document.getElementById("other-complaint");
+const complaintDescr = document.getElementById("complaint-description");
+const refund = document.getElementById("refund");
+const exchange = document.getElementById("exchange");
+const otherS = document.getElementById("other-solution");
+const solutionDescr = document.getElementById("solution-description");
+
+// invalid full-name
+name.value = "";
+email.value = "example@domain.com";
+orderNo.value = "2024001122";
+prCode.value = "Xa22-X123-Xb4";
+quantity.value = "5";
+damaged.checked = true;
+refund.checked = true;
+assert.isFalse(isValid(validateForm()));
+
+// invalid email
+name.value = "testing";
+email.value = "";
+assert.isFalse(isValid(validateForm()));
+
+// invalid order-no
+email.value = "example@domain.com";
+orderNo.value = "202400";
+assert.isFalse(isValid(validateForm()));
+
+// invalid product-code
+orderNo.value = "2024001122";
+prCode.value = "1a22-Xa23-Xb4";
+assert.isFalse(isValid(validateForm()));
+
+// invalid quantity
+prCode.value = "Xa22-X123-Xb4";
+quantity.value = 0;
+assert.isFalse(isValid(validateForm()));
+
+// invalid complaints-group
+quantity.value = 5;
+damaged.checked = false;
+nonConforming.checked = false;
+delayed.checked = false;
+otherC.checked = false;
+assert.isFalse(isValid(validateForm()));
+
+// invalid complaint-description
+otherC.checked = true;
+complaintDescr.value = "not enough chars";
+assert.isFalse(isValid(validateForm()));
+
+
+// invalid solutions-group
+complaintDescr.value = "A sentence with at least twenty characters";
+refund.checked = false;
+exchange.checked = false;
+otherS.checked = false;
+assert.isFalse(isValid(validateForm()));
+
+// invalid solutions-description
+otherS.checked = true;
+solutionDescr.value = "not enough chars";
+assert.isFalse(isValid(validateForm()));
+```
+
+You should call `isValid` when you try to submit the form.
+
+```js
+let flag = false;
+const temp = isValid;
+try {
+ isValid = (obj) => {flag = true};
+ document.getElementById("form").dispatchEvent(new Event("submit"));
+ assert.isTrue(flag);
+} finally {
+ isValid = temp;
+}
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+