Compare commits

...
Author SHA1 Message Date
egagofur 638b64b715 fix: validation by query param 2025-04-04 16:42:27 +07:00
egagofur d61cb4a215 fix: split zod validation register modal 2025-04-04 16:38:21 +07:00
Maulana SodiqinandGitHub 272db82c8c Merge pull request #18 from IMPHNEN/feat/register-integeration
feat: integeration register modal
2025-04-04 06:03:13 +07:00
3 changed files with 123 additions and 73 deletions
@@ -5,6 +5,7 @@ import { useRegister } from '../../_hooks/use-register';
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
import { UseFormReturn } from 'react-hook-form'; import { UseFormReturn } from 'react-hook-form';
import { TRegisterRequest } from '@imphnen-frontend-service/service'; import { TRegisterRequest } from '@imphnen-frontend-service/service';
import { useEffect } from 'react';
interface IModalFormRegisterProps { interface IModalFormRegisterProps {
isOpen: boolean; isOpen: boolean;
@@ -12,7 +13,7 @@ interface IModalFormRegisterProps {
} }
const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => { const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
const { form, onSubmit } = useRegister(); const { form, onSubmit, isStepOneValid } = useRegister();
const { const {
step: currentStep, step: currentStep,
@@ -25,6 +26,12 @@ const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
minValue: 1, minValue: 1,
}); });
useEffect(() => {
if (!isStepOneValid && currentStep === 2) {
prevStep();
}
}, [isStepOneValid, currentStep, prevStep]);
return ( return (
<Modal <Modal
className="py-[45px] min-w-[400px] lg:min-w-[455px] px-7" className="py-[45px] min-w-[400px] lg:min-w-[455px] px-7"
@@ -46,7 +53,11 @@ const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
<Modal.Content> <Modal.Content>
<form onSubmit={onSubmit} className="space-y-6"> <form onSubmit={onSubmit} className="space-y-6">
{currentStep === 1 && ( {currentStep === 1 && (
<StepOne form={form} nextStep={nextStep} onClose={onClose} /> <StepOne
form={form}
nextStep={nextStep}
isStepOneValid={isStepOneValid}
/>
)} )}
{currentStep === 2 && <StepTwo form={form} prevStep={prevStep} />} {currentStep === 2 && <StepTwo form={form} prevStep={prevStep} />}
</form> </form>
@@ -58,52 +69,59 @@ const ModalFormRegister = ({ isOpen, onClose }: IModalFormRegisterProps) => {
interface IStepOneProps { interface IStepOneProps {
form: UseFormReturn<TRegisterRequest, any, TRegisterRequest>; form: UseFormReturn<TRegisterRequest, any, TRegisterRequest>;
nextStep: () => void; nextStep: () => void;
onClose: () => void; isStepOneValid: boolean;
} }
const StepOne = ({ form, nextStep, onClose }: IStepOneProps) => ( const StepOne = ({ form, nextStep, isStepOneValid }: IStepOneProps) => {
<> return (
<ControlledInputField <>
control={form.control} <ControlledInputField
name="fullname" control={form.control}
label="Nama Lengkap" name="fullname"
placeholder="Masukkan Nama Lengkap" label="Nama Lengkap"
type="text" placeholder="Masukkan Nama Lengkap"
size="lg" type="text"
className="w-full" size="lg"
/> className="w-full"
<ControlledInputField />
control={form.control} <ControlledInputField
label="Email" control={form.control}
placeholder="Masukkan Email Anda" label="Email"
type="email" placeholder="Masukkan Email Anda"
name="email" type="email"
size="lg" name="email"
className="w-full" size="lg"
/> className="w-full"
<ControlledInputField />
control={form.control} <ControlledInputField
name="password" control={form.control}
label="Password" name="password"
placeholder="Masukkan Password" label="Password"
type="password" placeholder="Masukkan Password"
size="lg" type="password"
className="w-full" size="lg"
/> className="w-full"
<ControlledInputField />
control={form.control} <ControlledInputField
name="confirm_password" control={form.control}
label="Ulangi Password" name="confirm_password"
placeholder="Masukkan Ulang Password" label="Ulangi Password"
type="password" placeholder="Masukkan Ulang Password"
size="lg" type="password"
className="w-full" size="lg"
/> className="w-full"
<Button size="md" className="w-full" onClick={nextStep}> />
Selanjutnya <Button
</Button> size="md"
</> className="w-full"
); onClick={nextStep}
disabled={!isStepOneValid}
>
Selanjutnya
</Button>
</>
);
};
interface IStepTwoProps { interface IStepTwoProps {
form: UseFormReturn<TRegisterRequest, any, TRegisterRequest>; form: UseFormReturn<TRegisterRequest, any, TRegisterRequest>;
+30
View File
@@ -1,19 +1,48 @@
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { import {
authRegisterSchema, authRegisterSchema,
stepOneRegisterSchema,
TRegisterRequest, TRegisterRequest,
usePostRegister, usePostRegister,
} from '@imphnen-frontend-service/service'; } from '@imphnen-frontend-service/service';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useEffect, useState } from 'react';
export const useRegister = () => { export const useRegister = () => {
const postRegister = usePostRegister(); const postRegister = usePostRegister();
const form = useForm<TRegisterRequest>({ const form = useForm<TRegisterRequest>({
resolver: zodResolver(authRegisterSchema), resolver: zodResolver(authRegisterSchema),
mode: 'all', mode: 'all',
defaultValues: {
fullname: '',
email: '',
password: '',
confirm_password: '',
phone_number: '',
referral_code: '',
referred_by: '',
student_type: '',
},
}); });
const [stepValid, setStepValid] = useState(false);
useEffect(() => {
const subscription = form.watch(() => {
const { fullname, email, password, confirm_password } = form.getValues();
const valid = stepOneRegisterSchema.safeParse({
fullname,
email,
password,
confirm_password,
}).success;
setStepValid(valid);
});
return () => subscription.unsubscribe();
}, [form]);
const onSubmit = form.handleSubmit((data) => { const onSubmit = form.handleSubmit((data) => {
postRegister.mutate(data, { postRegister.mutate(data, {
onSuccess: (data) => toast.success(data.message), onSuccess: (data) => toast.success(data.message),
@@ -24,5 +53,6 @@ export const useRegister = () => {
return { return {
form, form,
onSubmit, onSubmit,
isStepOneValid: stepValid,
}; };
}; };
+29 -27
View File
@@ -16,7 +16,7 @@ export const authLoginSchema = z.object({
.min(1, 'Password tidak boleh kosong'), .min(1, 'Password tidak boleh kosong'),
}); });
export const authRegisterSchema = z export const stepOneRegisterSchema = z
.object({ .object({
email: z email: z
.string({ .string({
@@ -40,24 +40,6 @@ export const authRegisterSchema = z
.min(1, 'Password tidak boleh kosong') .min(1, 'Password tidak boleh kosong')
.min(8, 'Password harus lebih dari 8 karakter') .min(8, 'Password harus lebih dari 8 karakter')
.max(50, 'Password tidak boleh lebih dari 50 karakter'), .max(50, 'Password tidak boleh lebih dari 50 karakter'),
phone_number: z
.string({
required_error: 'Nomor telepon tidak boleh kosong',
invalid_type_error: 'Nomor telepon harus berupa string',
})
.min(1, 'Nomor telepon tidak boleh kosong')
.max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter'),
referral_code: z
.string()
.min(1, 'Kode referral tidak boleh kosong')
.max(4, 'Kode referral tidak boleh lebih dari 4 karakter')
.optional(),
referred_by: z
.string()
.min(1, 'Kode referral tidak boleh kosong')
.max(50, 'Kode referral tidak boleh lebih dari 50 karakter')
.optional(),
student_type: z.string(),
confirm_password: z confirm_password: z
.string({ .string({
required_error: 'Konfirmasi password tidak boleh kosong', required_error: 'Konfirmasi password tidak boleh kosong',
@@ -65,12 +47,32 @@ export const authRegisterSchema = z
.min(1, 'Konfirmasi password tidak boleh kosong') .min(1, 'Konfirmasi password tidak boleh kosong')
.max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'), .max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'),
}) })
.superRefine((val, ctx) => { .refine((data) => data.password === data.confirm_password, {
if (val.password !== val.confirm_password) { message: 'Password dan Konfirmasi Password harus sama',
ctx.addIssue({ path: ['confirm_password'],
code: z.ZodIssueCode.custom,
path: ['confirm_password'],
message: `No duplicates allowed.`,
});
}
}); });
const stepTwoRegisterSchema = z.object({
phone_number: z
.string({
required_error: 'Nomor telepon tidak boleh kosong',
invalid_type_error: 'Nomor telepon harus berupa string',
})
.min(1, 'Nomor telepon tidak boleh kosong')
.max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter'),
referral_code: z
.string()
.min(1, 'Kode referral tidak boleh kosong')
.max(4, 'Kode referral tidak boleh lebih dari 4 karakter')
.optional(),
referred_by: z
.string()
.min(1, 'Kode referral tidak boleh kosong')
.max(50, 'Kode referral tidak boleh lebih dari 50 karakter')
.optional(),
student_type: z.string(),
});
export const authRegisterSchema = stepOneRegisterSchema.and(
stepTwoRegisterSchema
);