diff --git a/apps/landing/src/app/(auth)/signup/_actions/signup-action.ts b/apps/landing/src/app/(auth)/signup/_actions/signup-action.ts new file mode 100644 index 0000000..a43aa4a --- /dev/null +++ b/apps/landing/src/app/(auth)/signup/_actions/signup-action.ts @@ -0,0 +1,15 @@ +'use server'; + +import { z } from 'zod'; +import { fetchPostSignin } from '../_http/fetch-post-signup'; +import { signupValidationSchema } from '../_validation/signup-validation'; + +export async function SignupAction( + request: z.infer +) { + const validRequest = signupValidationSchema.parse(request); + + const data = await fetchPostSignin({ ...validRequest }); + + return data; +} diff --git a/apps/landing/src/app/(auth)/signup/_components/signup-form.tsx b/apps/landing/src/app/(auth)/signup/_components/signup-form.tsx new file mode 100644 index 0000000..dcad592 --- /dev/null +++ b/apps/landing/src/app/(auth)/signup/_components/signup-form.tsx @@ -0,0 +1,255 @@ +'use client'; + +import { + Button, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, +} from '@components'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useMutation } from '@tanstack/react-query'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { LuLoader } from 'react-icons/lu'; +import { z } from 'zod'; +import { SignupAction } from '../_actions/signup-action'; +import { + signupValidationSchema, + stepOneSignupValidationSchema, + stepTwoSignupValidationSchema, +} from '../_validation/signup-validation'; + +export function SignupForm() { + const router = useRouter(); + const [step, setStep] = useState(1); + const [stepOneData, setStepOneData] = useState | null>(null); + + const { mutate, isPending, error } = useMutation({ + mutationFn: async (data: z.infer) => { + const result = await SignupAction(data); + + return { + ...result, + email: data.email, + }; + }, + onSuccess: ({ email }) => { + router.push(`/verification?ref=${email}`); + }, + onError: () => { + secondForm.reset(); + }, + }); + + const firstForm = useForm>({ + resolver: zodResolver(stepOneSignupValidationSchema), + defaultValues: { + email: '', + fullname: '', + password: '', + confirm_password: '', + }, + }); + + const secondForm = useForm>({ + resolver: zodResolver(stepTwoSignupValidationSchema), + defaultValues: { + phone_number: '', + referral_code: '', + referred_by: '', + student_type: '', + }, + }); + + const handleFirstSubmit = ( + values: z.infer + ) => { + setStepOneData(values); + setStep(2); + }; + + const handleSecondSubmit = ( + values: z.infer + ) => { + if (stepOneData) { + mutate({ ...stepOneData, ...values }); + } + }; + + return ( + <> + {step === 1 && ( +
+ + ( + + Full Name + + + + + + )} + /> + + ( + + Email + + + + + + )} + /> + + ( + + Password + + + + + + )} + /> + + ( + + Confirm Password + + + + + + )} + /> + + + + + )} + + {step === 2 && ( +
+ + {error && ( +
+ {(error as Error).message} +
+ )} + + ( + + Nomor Telepon + + + + + + )} + /> + +
+ ( + + Kode Referral + + + + + + )} + /> + + ( + + Referrer + + + + + + )} + /> +
+ + ( + + Status + + + + + + )} + /> + +
+ + +
+ + + )} + + ); +} diff --git a/apps/landing/src/app/(auth)/signup/_http/fetch-post-signup.ts b/apps/landing/src/app/(auth)/signup/_http/fetch-post-signup.ts new file mode 100644 index 0000000..3236c49 --- /dev/null +++ b/apps/landing/src/app/(auth)/signup/_http/fetch-post-signup.ts @@ -0,0 +1,34 @@ +import { fetcher } from '@/lib/fetcher'; +import { SignupValidationSchema } from '../_validation/signup-validation'; + +export async function fetchPostSignin({ + email, + password, + fullname, + phone_number, + student_type, + referral_code, + referred_by, +}: SignupValidationSchema) { + const { data, error, response } = await fetcher.POST('/v1/auth/register', { + body: { + email, + password, + fullname, + phone_number, + student_type, + referral_code, + referred_by, + }, + }); + + if (error) { + throw new Error(error.message); + } + + if (!response.ok) { + throw new Error('Someting went wrong, please try again later'); + } + + return data; +} diff --git a/apps/landing/src/app/(auth)/signup/_validation/signup-validation.ts b/apps/landing/src/app/(auth)/signup/_validation/signup-validation.ts new file mode 100644 index 0000000..1177ab1 --- /dev/null +++ b/apps/landing/src/app/(auth)/signup/_validation/signup-validation.ts @@ -0,0 +1,75 @@ +import { z } from 'zod'; + +export const stepOneSignupValidationSchema = z + .object({ + email: z + .string({ + required_error: 'Email tidak boleh kosong', + invalid_type_error: 'Email harus berupa string', + }) + .min(1, 'Email tidak boleh kosong') + .email('Email harus valid'), + fullname: z + .string({ + required_error: 'Nama tidak boleh kosong', + invalid_type_error: 'Nama harus berupa string', + }) + .min(1, 'Nama tidak boleh kosong') + .max(50, 'Nama tidak boleh lebih dari 50 karakter'), + password: z + .string({ + required_error: 'Password tidak boleh kosong', + invalid_type_error: 'Password harus berupa string', + }) + .min(1, 'Password tidak boleh kosong') + .min(8, 'Password harus lebih dari 8 karakter') + .max(50, 'Password tidak boleh lebih dari 50 karakter') + .regex( + /(?=.*[A-Z])/, + 'Password harus mengandung setidaknya satu huruf kapital' + ) + .regex( + /(?=.*[a-z])/, + 'Password harus mengandung setidaknya satu huruf kecil' + ) + .regex(/(?=.*\d)/, 'Password harus mengandung setidaknya satu angka') + .regex( + /(?=.*[!@#$%^&*()_\-+={}[\]|\\:;"'<>,.?/~`])/, + 'Password harus mengandung setidaknya satu karakter spesial' + ), + confirm_password: z + .string({ + required_error: 'Konfirmasi password tidak boleh kosong', + }) + .min(1, 'Konfirmasi password tidak boleh kosong') + .max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'), + }) + .refine((data) => data.password === data.confirm_password, { + message: 'Password dan Konfirmasi Password harus sama', + path: ['confirm_password'], + }); + +export const stepTwoSignupValidationSchema = z.object({ + phone_number: z + .string({ + required_error: 'Nomor telepon tidak boleh kosong', + invalid_type_error: 'Nomor telepon harus berupa string', + }) + .min(10, 'Nomor telepon tidak boleh kurang dari 10 karakter') + .max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter') + .regex(/^\d+$/, 'Nomor telepon hanya boleh berisi angka'), + referral_code: z + .string() + .max(4, 'Kode referral tidak boleh lebih dari 4 karakter') + .optional(), + referred_by: z + .string() + .max(50, 'Kode referral tidak boleh lebih dari 50 karakter') + .optional(), + student_type: z.string(), +}); + +export const signupValidationSchema = stepOneSignupValidationSchema.and( + stepTwoSignupValidationSchema +); +export type SignupValidationSchema = z.infer; diff --git a/apps/landing/src/app/(auth)/signup/page.tsx b/apps/landing/src/app/(auth)/signup/page.tsx index 6971c89..f86827a 100644 --- a/apps/landing/src/app/(auth)/signup/page.tsx +++ b/apps/landing/src/app/(auth)/signup/page.tsx @@ -1,9 +1,23 @@ +import { LogoSimple } from '@/app/_components/logo'; import { Metadata } from 'next'; +import { SignupForm } from './_components/signup-form'; export const metadata: Metadata = { - title: 'IMPHNEN - Signin', + title: 'IMPHNEN - Signup', }; export default function Page() { - return <>; + return ( + <> +
+ + +

+ Buat akunmu sekarang +

+
+ + + + ); }