diff --git a/src/assets/svg/Icons/OpenIcon.tsx b/src/assets/svg/Icons/OpenIcon.tsx new file mode 100644 index 00000000..fefec2a5 --- /dev/null +++ b/src/assets/svg/Icons/OpenIcon.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +const OpenIcon: React.FC = () => { + return ( +
+ + + +
+ ); +}; + +export default OpenIcon; diff --git a/src/components/TermsAgreementModal/index.tsx b/src/components/TermsAgreementModal/index.tsx new file mode 100644 index 00000000..52a56202 --- /dev/null +++ b/src/components/TermsAgreementModal/index.tsx @@ -0,0 +1,165 @@ +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import Tooltip from '../Tooltip'; +import OpenIcon from '../../assets/svg/Icons/OpenIcon'; +import { + menteeTermsAgreementModalSchema, + mentorTermsAgreementModalSchema, +} from '../../schemas'; + +type MentorFormData = z.infer; +type MenteeFormData = z.infer; + +interface TermsAgreementModalProps { + isOpen: boolean; + onClose: () => void; + onAgree: (data: MentorFormData | MenteeFormData) => void; + isMentor: boolean; + guideUrl: string; +} + +const TermsAgreementModal: React.FC = ({ + isOpen, + onClose, + onAgree, + isMentor, + guideUrl, +}) => { + const schema = isMentor + ? mentorTermsAgreementModalSchema + : menteeTermsAgreementModalSchema; + const { + register, + handleSubmit, + formState: { errors, isValid }, + } = useForm({ + resolver: zodResolver(schema), + mode: 'onChange', + defaultValues: isMentor + ? { agreed: false, canCommit: false } + : { agreed: false, consentGiven: false }, + }); + + const onSubmit = (data: MentorFormData | MenteeFormData) => { + if (isValid) { + onAgree(data); + } else { + console.error('Form is invalid. Errors:', errors); + } + }; + + if (!isOpen) return null; + + const guideType = isMentor ? 'Mentor' : 'Mentee'; + const title = isMentor + ? 'One Last Step to Join as a ScholarX Mentor' + : 'ScholarX Mentee Terms and Privacy'; + + return ( +
+
+
+
+

+ {title} +

+ +
+

+ ScholarX {guideType} Guide +

+
+ + + + + +
+
+ +
+ +
+ + {!isMentor && ( + <> +

+ Privacy Statement +

+

+ Sustainable Foundation Education assures that your video + submission will be used exclusively for application evaluation + purposes. We are committed to protecting your privacy and will + not use your video for any other activities, such as general + AI training or public distribution. Your personal information + and video content will be handled with the utmost + confidentiality. +

+ + )} + +
+ + {errors.agreed && ( +

+ {errors.agreed.message} +

+ )} + + + {(errors.canCommit ?? errors.consentGiven) && ( +

+ {errors.canCommit?.message ?? errors.consentGiven?.message} +

+ )} +
+
+ +
+ + +
+
+
+
+ ); +}; + +export default TermsAgreementModal; diff --git a/src/pages/MenteeRegistration/index.tsx b/src/pages/MenteeRegistration/index.tsx index 342e3131..be6a1d50 100644 --- a/src/pages/MenteeRegistration/index.tsx +++ b/src/pages/MenteeRegistration/index.tsx @@ -11,6 +11,7 @@ import { API_URL } from '../../constants'; import useProfile from '../../hooks/useProfile'; import { MenteeApplicationSchema } from '../../schemas'; import { MenteeApplication } from '../../types'; +import TermsAgreementModal from '../../components/TermsAgreementModal'; const steps = [ { @@ -35,9 +36,11 @@ const MenteeRegistration: React.FC = () => { setError, clearErrors, trigger, + getValues, formState: { errors }, } = useForm({ resolver: zodResolver(MenteeApplicationSchema), + mode: 'onChange', defaultValues: { firstName: user?.first_name, lastName: user?.last_name, @@ -51,6 +54,8 @@ const MenteeRegistration: React.FC = () => { const [currentStep, setCurrentStep] = useState(0); const [image, setImage] = useState(null); const [profilePic, setProfilePic] = useState(user?.image_url); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const handleProfilePicChange = (event: ChangeEvent) => { if (event.target.files != null) { @@ -89,7 +94,7 @@ const MenteeRegistration: React.FC = () => { } } - const output = await trigger(fields as [keyof MenteeApplication], { + const output = await trigger(fields as Array, { shouldFocus: true, }); @@ -101,10 +106,31 @@ const MenteeRegistration: React.FC = () => { setCurrentStep((prevStep) => prevStep - 1); }; - const onSubmit: SubmitHandler = async (data) => { - await applyForMentor(data); - if (image) { - await updateProfile({ profile: null, image }); + const onSubmit: SubmitHandler = async () => { + setIsModalOpen(true); + }; + + const handleModalAgree = async (agreedData: { + agreed: boolean; + consentGive?: boolean; + canCommit?: boolean; + }) => { + setIsModalOpen(false); + setIsSubmitting(true); + const formData = getValues(); + try { + const validatedData = MenteeApplicationSchema.parse({ + ...formData, + ...agreedData, + }); + await applyForMentor(validatedData); + if (image) { + await updateProfile({ profile: null, image }); + } + } catch (error) { + console.error('Error submitting application:', error); + } finally { + setIsSubmitting(false); } }; @@ -355,22 +381,6 @@ const MenteeRegistration: React.FC = () => { register={register} error={errors.submission} /> - -

Privacy Statement

-

- Sustainable Foundation Education assures that your video - submission will be used exclusively for application evaluation - purposes. We are committed to protecting your privacy and will not - use your video for any other activities, such as general AI - training or public distribution. Your personal information and - video content will be handled with the utmost confidentiality. -

)} {status === 'error' ? ( @@ -425,6 +435,7 @@ const MenteeRegistration: React.FC = () => { {currentStep === 2 && !applicationSuccess && ( )} + {currentStep < 2 && ( )} + {currentStep === 2 && !applicationSuccess && ( )} + {applicationSuccess && ( { )} + { + setIsModalOpen(false); + }} + onAgree={handleModalAgree} + isMentor={true} + guideUrl="https://docs.google.com/document/d/1uMMcGWJ35nblOj1zZ1XzJuYm-LOi1Lyj02yYRNsaOkY/" + /> ); }; diff --git a/src/schemas.ts b/src/schemas.ts index b72bec90..fb5df085 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -15,9 +15,6 @@ export const MenteeApplicationSchema = z.object({ .optional(), cv: z.string().min(1, { message: 'CV cannot be empty' }), isUndergrad: z.boolean(), - consentGiven: z.boolean().refine((val) => val, { - message: 'You must give your consent to proceed.', - }), graduatedYear: z .number({ invalid_type_error: 'Graduated year is required' }) .refine( @@ -43,6 +40,7 @@ export const MenteeApplicationSchema = z.object({ submission: z .string() .url({ message: 'Please submit a valid video submission' }), + consentGiven: z.boolean().optional(), }); export const MentorApplicationSchema = z.object({ @@ -75,9 +73,6 @@ export const MentorApplicationSchema = z.object({ noOfMentees: z.number().min(0, { message: 'Number of mentees must be greater than or equal to 0', }), - canCommit: z.boolean().refine((val) => val, { - message: 'You must mention if you can commit', - }), mentoredYear: z .number({ invalid_type_error: 'Mentored year is required' }) .or(z.number().min(0)) @@ -94,6 +89,7 @@ export const MentorApplicationSchema = z.object({ .url({ message: 'Invalid website URL' }) .optional() .or(z.literal('')), + canCommit: z.boolean().optional(), }); export const MenteeCheckInSchema = z.object({ @@ -104,3 +100,21 @@ export const MenteeCheckInSchema = z.object({ .url('Please provide a valid URL') .min(1, 'Please provide a media link'), }); + +export const mentorTermsAgreementModalSchema = z.object({ + agreed: z.boolean().refine((val) => val, { + message: 'You must agree to the ScholarX Mentor Guide', + }), + canCommit: z.boolean().refine((val) => val, { + message: 'You must mention if you can commit', + }), +}); + +export const menteeTermsAgreementModalSchema = z.object({ + agreed: z.boolean().refine((val) => val, { + message: 'You must agree to the ScholarX Mentee Guide', + }), + consentGiven: z.boolean().refine((val) => val, { + message: 'You must give consent to proceed', + }), +});