Skip to content

Commit 7629a26

Browse files
committed
feat: frontend contact us validation on get your plan form
1 parent 5679c3b commit 7629a26

File tree

7 files changed

+100
-25
lines changed

7 files changed

+100
-25
lines changed

apps/contact/app/api/get-plan/route.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import z from "zod";
66
const { NOTION_GET_PLAN_DATABASE_ID } = process.env;
77

88
const bodyValidationSchema = z.object({
9-
name: z
10-
.string()
11-
.min(3, { message: "Name must be at least 3 characters long" }),
9+
name: z.string(),
1210
email: z
1311
.email({ message: "Invalid email adress" })
1412
.min(1, { message: "Required field" }),

apps/website/src/components/Field.astro

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ type TextAreaProps = {
2222
2323
type Props = BaseProps & (InputProps | TextAreaProps);
2424
25-
const { classNames, labelProps, label, errorText, onDark } = Astro.props;
25+
const props = Astro.props as Props;
26+
27+
28+
const { classNames, labelProps, label, errorText, onDark } = props;
29+
const { isTextArea } = props;
30+
31+
const fieldId = isTextArea ? props.textAreaProps.id : props.inputProps.id
2632
---
2733

2834
<div
@@ -108,15 +114,16 @@ const { classNames, labelProps, label, errorText, onDark } = Astro.props;
108114
[&>span:nth-child(2)]:hidden
109115
peer-[&:not(:placeholder-shown):not(:focus):invalid]/input:[&>span:first-child]:hidden
110116
peer-[&:not(:placeholder-shown):not(:focus):invalid]/input:[&>span:nth-child(2)]:inline
111-
peer-[&:not(:placeholder-shown):not(:focus):invalid]/input:text-red-500`,
117+
peer-[&:not(:placeholder-shown):not(:focus):invalid]/input:text-red-500
118+
`,
112119
{
113120
"text-secondary": !onDark,
114121
"text-neutral-50": onDark,
115122
},
116123
)}
117124
>
118-
<span>{label}</span>
119-
<span class="truncate text-ellipsis">{errorText || "Invalid field"}</span>
125+
<span id=`label-value-${fieldId}`>{label}</span>
126+
<span id=`error-text-value-${fieldId}` class="truncate text-ellipsis">{errorText || "Invalid field"}</span>
120127
</label>
121128
<div
122129
class={classnames(

apps/website/src/components/staff-agmentation/GetYourPlanForm.astro

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import "../../styles/loader.css";
2020
maxlength: "100",
2121
placeholder: formContent.frontmatter.fullname,
2222
required: true,
23-
minlength: 2,
2423
}}
2524
labelProps={{
2625
for: "form-full-name",
@@ -103,7 +102,9 @@ import "../../styles/loader.css";
103102
"get-your-plan-section",
104103
);
105104

106-
const form = document.getElementById("get-your-plan-form");
105+
const form = document.getElementById(
106+
"get-your-plan-form",
107+
) as HTMLFormElement | null;
107108
/* Page form elements */
108109
const submitBtn = form?.querySelector("#form-submit-button");
109110
const submitBtnElems = submitBtn?.querySelectorAll("*");
@@ -129,8 +130,21 @@ import "../../styles/loader.css";
129130
const modalFormLoader =
130131
modalFormSubmitBtn?.querySelector("#modal-form-loader");
131132

132-
const handleCloseModal = () => {
133-
modalForm?.close();
133+
const handleValidation = (
134+
input: HTMLInputElement | null,
135+
errorMessage: string,
136+
form: HTMLFormElement | null,
137+
) => {
138+
if (!input) return false;
139+
140+
const label = form?.querySelector<HTMLLabelElement>(
141+
`label[for="${input.id}"]`,
142+
);
143+
const placeholderSpan = label?.querySelector("span:first-child");
144+
const errorSpan = label?.querySelector("span:nth-child(2)");
145+
placeholderSpan?.classList.add("hidden");
146+
errorSpan?.classList.add("!inline", "text-red-500");
147+
if (errorSpan) errorSpan.textContent = errorMessage;
134148
};
135149

136150
const showModalLoader = () => {
@@ -150,7 +164,38 @@ import "../../styles/loader.css";
150164
submitBtnLoader?.classList.remove("!block");
151165
};
152166

153-
if (form) {
167+
const handleFieldReset = (input: HTMLInputElement | null) => {
168+
if (!input) return;
169+
170+
const spanErr = form?.querySelector(`#error-text-value-${input.id}`);
171+
const placeholderSpan = form?.querySelector(`#label-value-${input.id}`);
172+
173+
input.addEventListener("click", () => {
174+
placeholderSpan?.classList.remove("hidden");
175+
spanErr?.classList.remove("!inline", "text-red-500");
176+
});
177+
};
178+
179+
const handleConsentReset = (checkbox: HTMLInputElement | null) => {
180+
if (!checkbox) return;
181+
182+
checkbox.addEventListener("click", () => {
183+
checkbox.classList.replace("border-red-600", "border-gray-300");
184+
checkbox.classList.remove("border-2");
185+
});
186+
};
187+
188+
const emailInput = form?.querySelector<HTMLInputElement>("#form-email");
189+
const nameInput =
190+
form?.querySelector<HTMLInputElement>("#form-full-name");
191+
const consentInput =
192+
form?.querySelector<HTMLInputElement>("#form-consent");
193+
194+
if (form && emailInput && nameInput && consentInput) {
195+
handleFieldReset(nameInput);
196+
handleFieldReset(emailInput);
197+
handleConsentReset(consentInput);
198+
154199
form.addEventListener("submit", async (e) => {
155200
e.preventDefault();
156201
const formData = new FormData(e.target as HTMLFormElement);
@@ -161,21 +206,46 @@ import "../../styles/loader.css";
161206
const mainChallenge = formData.get("form-challenge");
162207
const expectedStartDate = formData.get("form-start-date");
163208

164-
const emailInput = form.querySelector<HTMLInputElement>("#form-email");
209+
const consentInput =
210+
form?.querySelector<HTMLInputElement>("#form-consent");
165211

212+
const isNameInputValid =
213+
nameInput.validity.valid && !nameInput.validity.valueMissing;
214+
const isEmailInputValid =
215+
emailInput.validity.valid && !emailInput.validity.valueMissing;
216+
const isConsentInputValid = consentInput?.validity.valid;
166217

167-
if (!name || !email || !consent) {
168-
notificationElem.classList.add("text-red-500");
218+
if (!isNameInputValid) {
219+
handleValidation(
220+
nameInput,
221+
notification.frontmatter.fullnameLength,
222+
form,
223+
);
224+
}
225+
226+
if (!isEmailInputValid) {
227+
handleValidation(
228+
emailInput,
229+
notification.frontmatter.emailInvalid,
230+
form,
231+
);
232+
}
169233

170-
if (!name) {
171-
notificationElem.textContent = notification.frontmatter.fullnameLength;
172-
} else if (!email) {
173-
notificationElem.textContent = notification.frontmatter.emailInvalid;
174-
} else if (!consent) {
175-
notificationElem.textContent = notification.frontmatter.consentRequired;
234+
if (
235+
!nameInput.validity.valid ||
236+
!emailInput.validity.valid ||
237+
!isConsentInputValid
238+
) {
239+
if (!consent) {
240+
consentInput?.classList.replace(
241+
"border-gray-300",
242+
"border-red-600",
243+
);
244+
consentInput?.classList.add("border-2");
176245
}
177246
return;
178247
}
248+
notificationElem.textContent = "";
179249

180250
const isPageSubmitter =
181251
(e.submitter as HTMLButtonElement).value === "page";

apps/website/src/components/staff-agmentation/GetYourPlanModal.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Field from "../Field.astro";
33
import Modal from "../Modal.astro";
44
import * as formContent from "../../content/contact/get-your-plan-form.md";
55
import Select from "../Select.astro";
6-
import { quarterLabelInMonthsAhead } from "../../utils/quarter";
6+
import { quarterLabelInMonthsAhead } from "../../utils/quarter.ts";
77
88
const computedStartDates = (formContent.frontmatter.startDates as string[]).map(
99
(d) => (d === "quarter-label" ? quarterLabelInMonthsAhead(6) : d),

apps/website/src/content/contact/form.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
fullname: "Full name *"
3-
fullnameLength: "Name must be at least 2 characters."
3+
fullnameLength: "Name is required"
44
requiredField: "This is a required field."
55
email: "E-mail *"
66
emailInvalid: "Email you entered is not valid."

apps/website/src/content/contact/get-your-plan-form.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
fullname: "Full name *"
3-
fullnameLength: "Name must be at least 3 characters."
3+
fullnameLength: "Name is required"
44
requiredField: "This is a required field."
55
email: "E-mail *"
66
emailInvalid: "Email you entered is not valid."

apps/website/src/content/contact/notification.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
success: "Thank you for contacting us! We will reach soon."
33
error: "Looks like something went wrong. Please try again."
4-
fullnameLength: "Name must be at least 2 characters."
4+
fullnameLength: "Name is required."
55
emailInvalid: "Please enter a valid email address."
66
consentRequired: "You must agree to the privacy consent before continuing."
77
projectDescriptionRequired: "Project description is required."

0 commit comments

Comments
 (0)