Skip to content

Commit db504bc

Browse files
metamyteeeJXP25Princekumarofficial
authored
Recaptcha-fix-JAF (#381)
Co-authored-by: Jai Pannu <142983705+JaiPannu-IITI@users.noreply.github.com> Co-authored-by: Prince Kumar <x.prince.x@outlook.com> Co-authored-by: Jai Pannu <142983705+JXP25@users.noreply.github.com>
1 parent 002c68a commit db504bc

2 files changed

Lines changed: 205 additions & 128 deletions

File tree

src/components/JAF/JAF.tsx

Lines changed: 148 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22
import "antd/dist/reset.css";
3-
import { useEffect, useState } from "react";
3+
import { useEffect, useState, useRef } from "react";
44
import { Row, Col, Steps, Space, Button, Alert } from "antd";
55
import { FormikWizard, RenderProps } from "formik-wizard-form";
66
import axios from "axios";
@@ -17,6 +17,7 @@ import {
1717
recruiterDetailsValidationSchema,
1818
jobDetailsValidationSchema,
1919
} from "../../validation/jaf.validation";
20+
import ReCAPTCHA from "react-google-recaptcha";
2021

2122
const baseUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
2223
const { Step } = Steps;
@@ -72,9 +73,12 @@ const getErrorMessages = (errors: any): string[] => {
7273
function JAF() {
7374
const accessToken = Cookies.get("accessToken");
7475
const [isLoading, setIsLoading] = useState(true);
76+
const [captchaToken, setCaptchaToken] = useState("");
77+
const [showCaptcha, setShowCaptcha] = useState(false);
78+
const recaptchaRef = useRef<ReCAPTCHA | null>(null);
7579
const [isSubmitting, setIsSubmitting] = useState(false);
7680

77-
useEffect(() => {
81+
useEffect(() => {
7882
const timer = setTimeout(() => setIsLoading(false), 500);
7983
return () => clearTimeout(timer);
8084
}, []);
@@ -88,105 +92,102 @@ function JAF() {
8892
}
8993

9094
return (
91-
<div className="flex flex-col w-full gap-8 md:gap-20 p-2 md:p-10"> <FormikWizard
95+
<div className="flex flex-col w-full gap-8 md:gap-20 p-2 md:p-10">
96+
<FormikWizard
9297
key="jaf-form-wizard"
9398
initialValues={DEFAULT_FORM_VALUES}
9499
onSubmit={async (values: JAFFormValues) => {
95-
setIsSubmitting(true);
96-
try {
97-
// Filter and format recruiters
98-
const recruiters = [1, 2, 3]
99-
.map((i) => ({
100-
name: values[`recName${i}`] || "",
101-
designation: values[`designation${i}`] || "",
102-
email: values[`email${i}`] || "",
103-
contact: values[`phoneNumber${i}`]
104-
? "+91 " + values[`phoneNumber${i}`]
105-
: "",
106-
landline: values[`landline${i}`] || "",
107-
}))
108-
.filter(
109-
(r) =>
110-
r.name.trim() ||
111-
r.designation.trim() ||
112-
r.email.trim() ||
113-
r.contact.trim() ||
114-
r.landline.trim(),
115-
);
100+
if (!captchaToken) {
101+
setShowCaptcha(true);
102+
toast.error("Please complete the CAPTCHA to submit.");
103+
return;
104+
}
116105

117-
// Ensure at least primary recruiter is present
118-
if (recruiters.length === 0) {
119-
toast.error("At least one recruiter contact is required");
120-
return;
121-
}
106+
const recruiters = [1, 2, 3]
107+
.map((i) => ({
108+
name: values[`recName${i}`] || "",
109+
designation: values[`designation${i}`] || "",
110+
email: values[`email${i}`] || "",
111+
contact: values[`phoneNumber${i}`] ? "+91 " + values[`phoneNumber${i}`] : "",
112+
landline: values[`landline${i}`] || "",
113+
}))
114+
.filter(
115+
(r) =>
116+
r.name.trim() ||
117+
r.designation.trim() ||
118+
r.email.trim() ||
119+
r.contact.trim() ||
120+
r.landline.trim(),
121+
);
122122

123-
// Assemble payload matching backend DTO structure exactly
124-
const payload: JafDto = {
125-
job: {
126-
seasonId: values.seasonId,
127-
role: values.role,
128-
description: values.description || undefined,
129-
recruiterDetailsFilled: recruiters,
130-
attachments: values.attachments?.length
131-
? values.attachments.map((file) =>
132-
typeof file === "string" ? file : file.name,
133-
)
134-
: undefined,
135-
others: values.jobOthers || undefined,
136-
skills: values.skills?.length ? values.skills : undefined,
137-
location: values.location,
138-
minNoOfHires: values.minNoOfHires
139-
? Number(values.minNoOfHires)
140-
: undefined,
141-
expectedNoOfHires: values.expectedNoOfHires
142-
? Number(values.expectedNoOfHires)
143-
: undefined,
144-
offerLetterReleaseDate: values.offerLetterReleaseDate
145-
? new Date(values.offerLetterReleaseDate)
146-
: undefined,
147-
joiningDate: values.joiningDate
148-
? new Date(values.joiningDate)
149-
: undefined,
150-
duration: values.duration || undefined,
151-
selectionProcedure: {
152-
selectionMode: values.selectionMode,
153-
shortlistFromResume: values.shortlistFromResume,
154-
groupDiscussion: values.groupDiscussion,
155-
tests: values.tests || [],
156-
interviews: values.interviews || [],
157-
others: values.others || undefined,
158-
requirements:
159-
values.numberOfMembers ||
160-
values.numberOfRooms ||
161-
values.otherRequirements
162-
? {
163-
numberOfMembers: values.numberOfMembers
164-
? Number(values.numberOfMembers)
165-
: undefined,
166-
numberOfRooms: values.numberOfRooms
167-
? Number(values.numberOfRooms)
168-
: undefined,
169-
otherRequirements:
170-
values.otherRequirements || undefined,
171-
}
172-
: undefined,
173-
},
123+
if (recruiters.length === 0) {
124+
toast.error("At least one recruiter contact is required");
125+
return;
126+
}
127+
const payload: JafDto & { captchaToken: string } = {
128+
job: {
129+
seasonId: values.seasonId,
130+
role: values.role,
131+
description: values.description || undefined,
132+
recruiterDetailsFilled: recruiters,
133+
attachments: values.attachments?.length
134+
? values.attachments.map((file) =>
135+
typeof file === "string" ? file : file.name,
136+
)
137+
: undefined,
138+
others: values.jobOthers || undefined,
139+
skills: values.skills?.length ? values.skills : undefined,
140+
location: values.location,
141+
minNoOfHires: values.minNoOfHires
142+
? Number(values.minNoOfHires)
143+
: undefined,
144+
expectedNoOfHires: values.expectedNoOfHires
145+
? Number(values.expectedNoOfHires)
146+
: undefined,
147+
offerLetterReleaseDate: values.offerLetterReleaseDate
148+
? new Date(values.offerLetterReleaseDate)
149+
: undefined,
150+
joiningDate: values.joiningDate
151+
? new Date(values.joiningDate)
152+
: undefined,
153+
duration: values.duration || undefined,
154+
selectionProcedure: {
155+
selectionMode: values.selectionMode,
156+
shortlistFromResume: values.shortlistFromResume,
157+
groupDiscussion: values.groupDiscussion,
158+
tests: values.tests || [],
159+
interviews: values.interviews || [],
160+
others: values.others || undefined,
161+
requirements:
162+
values.numberOfMembers ||
163+
values.numberOfRooms ||
164+
values.otherRequirements
165+
? {
166+
numberOfMembers: values.numberOfMembers
167+
? Number(values.numberOfMembers)
168+
: undefined,
169+
numberOfRooms: values.numberOfRooms
170+
? Number(values.numberOfRooms)
171+
: undefined,
172+
otherRequirements:
173+
values.otherRequirements || undefined,
174+
}
175+
: undefined,
174176
},
175-
salaries: values.salaries || [],
176-
};
177+
},
178+
salaries: values.salaries || [],
179+
captchaToken: captchaToken,
180+
};
177181

178-
// Submit to backend
182+
try {
179183
await axios.post(`${baseUrl}${API_ENDPOINTS.SUBMIT_JAF}`, payload, {
180184
headers: {
181185
Authorization: `Bearer ${accessToken}`,
182186
"Content-Type": "application/json",
183187
},
184188
});
185189

186-
toast.success(
187-
"JAF Form submitted successfully! Your application has been received.",
188-
);
189-
// Reset form or redirect as needed
190+
toast.success("JAF Form submitted successfully! Your application has been received.");
190191
window.location.reload();
191192
} catch (error: any) {
192193
console.error("JAF submission error:", error);
@@ -195,9 +196,13 @@ function JAF() {
195196
error.response?.data?.message ||
196197
error.response?.data?.error ||
197198
"Failed to submit JAF form. Please try again.";
199+
198200
toast.error(errorMessage);
199201
} finally {
200202
setIsSubmitting(false);
203+
setCaptchaToken("");
204+
setShowCaptcha(false);
205+
recaptchaRef.current?.reset();
201206
}
202207
}}
203208
validateOnNext
@@ -225,6 +230,7 @@ function JAF() {
225230
isNextDisabled,
226231
isPrevDisabled,
227232
errors,
233+
isSubmitting,
228234
}: RenderProps) => {
229235
const errorMessages = getErrorMessages(errors);
230236

@@ -245,37 +251,61 @@ function JAF() {
245251

246252
{renderComponent()}
247253

248-
<Row justify="center" className="px-2 md:px-0">
249-
<Space
250-
size="large"
251-
className="w-full max-w-xs flex justify-center"
252-
>
253-
<Button
254-
disabled={isPrevDisabled || isSubmitting}
255-
onClick={handlePrev}
256-
className="flex-1 min-w-20"
257-
size="large"
258-
>
259-
Previous
260-
</Button>
261-
<Button
262-
disabled={isNextDisabled || isSubmitting}
263-
onClick={handleNext}
264-
className="flex-1 min-w-20"
265-
size="large"
266-
type="primary"
267-
loading={isSubmitting && currentStepIndex === 2}
268-
>
269-
{isSubmitting && currentStepIndex === 2
270-
? "Submitting..."
271-
: currentStepIndex === 2
272-
? "Finish"
273-
: "Next"}
274-
</Button>
275-
</Space>
276-
</Row>
277-
278-
{/* Show validation errors below the finish button */}
254+
{currentStepIndex === 2 ? (
255+
<Row justify="center" className="pt-6">
256+
<div className="flex flex-col items-center gap-4">
257+
{showCaptcha && (
258+
<ReCAPTCHA
259+
ref={recaptchaRef}
260+
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!}
261+
onChange={(token) => setCaptchaToken(token || "")}
262+
/>
263+
)}
264+
<div className="flex gap-2">
265+
<Button
266+
disabled={isPrevDisabled || isSubmitting}
267+
onClick={handlePrev}
268+
className="min-w-20"
269+
size="large"
270+
>
271+
Previous
272+
</Button>
273+
<Button
274+
disabled={isPrevDisabled || isSubmitting}
275+
onClick={handleNext}
276+
className="min-w-20"
277+
size="large"
278+
type="primary"
279+
loading={isSubmitting && currentStepIndex === 2}
280+
>
281+
Finish
282+
</Button>
283+
</div>
284+
</div>
285+
</Row>
286+
) : (
287+
<Row justify="center" className="px-2 md:px-0">
288+
<Space size="large" className="w-full max-w-xs flex justify-center">
289+
<Button
290+
disabled={isPrevDisabled}
291+
onClick={handlePrev}
292+
className="flex-1 min-w-20"
293+
size="large"
294+
>
295+
Previous
296+
</Button>
297+
<Button
298+
disabled={isNextDisabled}
299+
onClick={handleNext}
300+
className="flex-1 min-w-20"
301+
size="large"
302+
type="primary"
303+
>
304+
Next
305+
</Button>
306+
</Space>
307+
</Row>
308+
)}
279309
{currentStepIndex === 2 &&
280310
isNextDisabled &&
281311
errorMessages.length > 0 && (

0 commit comments

Comments
 (0)