diff --git a/frontend/app/(dashboard)/profile/page.tsx b/frontend/app/(dashboard)/profile/page.tsx new file mode 100644 index 00000000..aa6788c5 --- /dev/null +++ b/frontend/app/(dashboard)/profile/page.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { toast } from "sonner"; +import { updateProfile, changePassword } from "@/lib/api/auth.api"; +import { useAuthStore } from "@/stores/auth.store"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +const profileSchema = z.object({ + firstName: z.string().min(1, "First name is required"), + lastName: z.string().min(1, "Last name is required"), + walletAddress: z.string().optional(), +}); + +const passwordSchema = z + .object({ + currentPassword: z.string().min(1, "Current password is required"), + newPassword: z.string().min(8, "New password must be at least 8 characters"), + confirmPassword: z.string().min(1, "Please confirm your new password"), + }) + .refine((d) => d.newPassword === d.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], + }); + +type ProfileValues = z.infer; +type PasswordValues = z.infer; + +export default function ProfilePage() { + const user = useAuthStore((s) => s.user); + const setUser = useAuthStore((s) => s.setUser); + + const { + register: regProfile, + handleSubmit: handleProfile, + reset: resetProfile, + formState: { errors: profileErrors, isSubmitting: profileSubmitting }, + } = useForm({ resolver: zodResolver(profileSchema) }); + + const { + register: regPassword, + handleSubmit: handlePassword, + reset: resetPassword, + formState: { errors: passwordErrors, isSubmitting: passwordSubmitting }, + } = useForm({ resolver: zodResolver(passwordSchema) }); + + useEffect(() => { + if (user) { + resetProfile({ + firstName: user.firstName, + lastName: user.lastName, + walletAddress: user.walletAddress ?? "", + }); + } + }, [user, resetProfile]); + + const onProfileSubmit = async (values: ProfileValues) => { + try { + const updated = await updateProfile({ + firstName: values.firstName, + lastName: values.lastName, + walletAddress: values.walletAddress || undefined, + }); + setUser(updated); + toast.success("Profile updated successfully"); + } catch (err: unknown) { + toast.error(err instanceof Error ? err.message : "Failed to update profile"); + } + }; + + const onPasswordSubmit = async (values: PasswordValues) => { + try { + const { message } = await changePassword(values.currentPassword, values.newPassword); + toast.success(message); + resetPassword(); + } catch (err: unknown) { + toast.error(err instanceof Error ? err.message : "Failed to change password"); + } + }; + + return ( +
+

Profile

+ + + + Personal Information + + +
+
+ + + {profileErrors.firstName && ( +

{profileErrors.firstName.message}

+ )} +
+ +
+ + + {profileErrors.lastName && ( +

{profileErrors.lastName.message}

+ )} +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+ + + + Change Password + + +
+
+ + + {passwordErrors.currentPassword && ( +

{passwordErrors.currentPassword.message}

+ )} +
+ +
+ + + {passwordErrors.newPassword && ( +

{passwordErrors.newPassword.message}

+ )} +
+ +
+ + + {passwordErrors.confirmPassword && ( +

{passwordErrors.confirmPassword.message}

+ )} +
+ + +
+
+
+
+ ); +}