diff --git a/apps/landing/src/app/(auth)/forgot-password/_actions/forgot-password-action.ts b/apps/landing/src/app/(auth)/forgot-password/_actions/forgot-password-action.ts
index 07958f8..2149c4e 100644
--- a/apps/landing/src/app/(auth)/forgot-password/_actions/forgot-password-action.ts
+++ b/apps/landing/src/app/(auth)/forgot-password/_actions/forgot-password-action.ts
@@ -17,8 +17,6 @@ export async function ForgotPasswordAction(
},
});
- console.log(data, error);
-
if (error) throw new Error(error.message);
return data;
diff --git a/apps/landing/src/app/(auth)/reset-password/_actions/reset-password-actions.ts b/apps/landing/src/app/(auth)/reset-password/_actions/reset-password-actions.ts
new file mode 100644
index 0000000..b4a8a1a
--- /dev/null
+++ b/apps/landing/src/app/(auth)/reset-password/_actions/reset-password-actions.ts
@@ -0,0 +1,28 @@
+'use server';
+
+import { fetcher } from '@/lib/fetcher';
+import {
+ resetPasswordValidationSchema,
+ ResetPasswordValidationSchema,
+} from '../_validation/reset-password-validation';
+
+export async function resetPasswordAction(
+ request: ResetPasswordValidationSchema
+) {
+ const validRequest = resetPasswordValidationSchema.parse(request);
+
+ if (validRequest.confirm_password !== validRequest.confirm_password) {
+ throw new Error('Password missmatch');
+ }
+
+ const { data, error } = await fetcher.POST('/v1/auth/new-password', {
+ body: {
+ password: validRequest.password,
+ token: validRequest.token,
+ },
+ });
+
+ if (error) throw new Error(error.message);
+
+ return data;
+}
diff --git a/apps/landing/src/app/(auth)/reset-password/_components/reset-password-form.tsx b/apps/landing/src/app/(auth)/reset-password/_components/reset-password-form.tsx
new file mode 100644
index 0000000..c4289b3
--- /dev/null
+++ b/apps/landing/src/app/(auth)/reset-password/_components/reset-password-form.tsx
@@ -0,0 +1,73 @@
+'use client';
+
+import {
+ Button,
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input,
+} from '@components';
+import { LuLoaderCircle } from 'react-icons/lu';
+import { usePostResetPassword } from '../_hooks/use-post-reset-password';
+import { useResetPasswordForm } from '../_hooks/use-reset-password-form';
+import { ResetPasswordValidationSchema } from '../_validation/reset-password-validation';
+
+export function ResetPasswordForm() {
+ const form = useResetPasswordForm();
+ const { mutate, error, isPending } = usePostResetPassword(form);
+
+ const onSubmit = (values: ResetPasswordValidationSchema) => {
+ mutate(values);
+ };
+
+ return (
+
+
+ );
+}
diff --git a/apps/landing/src/app/(auth)/reset-password/_hooks/use-post-reset-password.ts b/apps/landing/src/app/(auth)/reset-password/_hooks/use-post-reset-password.ts
new file mode 100644
index 0000000..80f89ec
--- /dev/null
+++ b/apps/landing/src/app/(auth)/reset-password/_hooks/use-post-reset-password.ts
@@ -0,0 +1,24 @@
+import { useMutation } from '@tanstack/react-query';
+import { useRouter } from 'next/navigation';
+import { toast } from 'sonner';
+import { resetPasswordAction } from '../_actions/reset-password-actions';
+import { useResetPasswordForm } from './use-reset-password-form';
+
+export function usePostResetPassword(
+ form: ReturnType
+) {
+ const router = useRouter();
+
+ return useMutation({
+ mutationFn: resetPasswordAction,
+ onSuccess: ({ message }) => {
+ form.reset();
+ router.replace('/signin');
+ toast.success(message);
+ },
+ onError: ({ message }) => {
+ form.reset();
+ toast.error(message);
+ },
+ });
+}
diff --git a/apps/landing/src/app/(auth)/reset-password/_hooks/use-reset-password-form.ts b/apps/landing/src/app/(auth)/reset-password/_hooks/use-reset-password-form.ts
new file mode 100644
index 0000000..4cdbb1c
--- /dev/null
+++ b/apps/landing/src/app/(auth)/reset-password/_hooks/use-reset-password-form.ts
@@ -0,0 +1,22 @@
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useSearchParams } from 'next/navigation';
+import { useForm } from 'react-hook-form';
+
+import {
+ resetPasswordValidationSchema,
+ ResetPasswordValidationSchema,
+} from '../_validation/reset-password-validation';
+
+export function useResetPasswordForm() {
+ const searchParams = useSearchParams();
+ const tokenFromQuery = searchParams.get('token') ?? '';
+
+ return useForm({
+ resolver: zodResolver(resetPasswordValidationSchema),
+ defaultValues: {
+ password: '',
+ confirm_password: '',
+ token: tokenFromQuery,
+ },
+ });
+}
diff --git a/apps/landing/src/app/(auth)/reset-password/_validation/reset-password-validation.ts b/apps/landing/src/app/(auth)/reset-password/_validation/reset-password-validation.ts
new file mode 100644
index 0000000..1261ca8
--- /dev/null
+++ b/apps/landing/src/app/(auth)/reset-password/_validation/reset-password-validation.ts
@@ -0,0 +1,31 @@
+import { z } from 'zod';
+
+export const resetPasswordValidationSchema = z
+ .object({
+ password: z
+ .string({
+ required_error: 'Password tidak boleh kosong',
+ invalid_type_error: 'Password harus berupa string',
+ })
+ .min(8, 'Password harus minimal 8 karakter')
+ .max(50, 'Password tidak boleh lebih dari 50 karakter')
+ .regex(
+ /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*\W).+$/,
+ 'Password harus mengandung setidaknya satu huruf kapital, satu huruf kecil, satu angka, dan satu karakter spesial'
+ ),
+ confirm_password: z
+ .string({
+ required_error: 'Konfirmasi password tidak boleh kosong',
+ })
+ .min(8, 'Konfirmasi password harus minimal 8 karakter')
+ .max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'),
+ token: z.string(),
+ })
+ .refine((data) => data.password === data.confirm_password, {
+ message: 'Password dan Konfirmasi Password harus sama',
+ path: ['confirm_password'],
+ });
+
+export type ResetPasswordValidationSchema = z.infer<
+ typeof resetPasswordValidationSchema
+>;
diff --git a/apps/landing/src/app/(auth)/reset-password/page.tsx b/apps/landing/src/app/(auth)/reset-password/page.tsx
new file mode 100644
index 0000000..6378290
--- /dev/null
+++ b/apps/landing/src/app/(auth)/reset-password/page.tsx
@@ -0,0 +1,30 @@
+import { LogoSimple } from '@/app/_components/logo';
+import { redirect } from 'next/navigation';
+import { use } from 'react';
+import { ResetPasswordForm } from './_components/reset-password-form';
+
+export const dynamic = 'force-dynamic';
+
+export default function Page({
+ searchParams,
+}: {
+ searchParams: Promise<{ token?: string }>;
+}) {
+ const { token } = use(searchParams);
+
+ if (!token) redirect('/signin');
+
+ return (
+ <>
+
+
+
+
+ Ubah passwordmu
+
+
+
+
+ >
+ );
+}