148 lines
5.0 KiB
TypeScript
148 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { useAuthStore } from "@/lib/auth-store";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
|
import { Button } from "@/components/ui/Button";
|
|
import { Input } from "@/components/ui/Input";
|
|
import { User, Lock } from "lucide-react";
|
|
import api from "@/lib/api";
|
|
import { toast } from "sonner";
|
|
|
|
export default function ProfilePage() {
|
|
const user = useAuthStore((s) => s.user);
|
|
|
|
const [infoForm, setInfoForm] = useState({
|
|
firstName: (user as any)?.firstName ?? user?.name?.split(" ")[0] ?? "",
|
|
lastName: (user as any)?.lastName ?? user?.name?.split(" ").slice(1).join(" ") ?? "",
|
|
email: user?.email ?? "",
|
|
});
|
|
|
|
const [pwForm, setPwForm] = useState({
|
|
currentPassword: "",
|
|
newPassword: "",
|
|
confirmPassword: "",
|
|
});
|
|
|
|
const updateInfo = useMutation({
|
|
mutationFn: async () => {
|
|
await api.patch("/api/v1/auth/me", {
|
|
firstName: infoForm.firstName,
|
|
lastName: infoForm.lastName,
|
|
email: infoForm.email,
|
|
});
|
|
},
|
|
onSuccess: () => toast.success("Profile updated!"),
|
|
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to update profile"),
|
|
});
|
|
|
|
const resetPassword = useMutation({
|
|
mutationFn: async () => {
|
|
if (pwForm.newPassword !== pwForm.confirmPassword) {
|
|
throw new Error("Passwords do not match");
|
|
}
|
|
await api.post("/api/v1/auth/change-password", {
|
|
currentPassword: pwForm.currentPassword,
|
|
newPassword: pwForm.newPassword,
|
|
});
|
|
},
|
|
onSuccess: () => {
|
|
toast.success("Password changed successfully!");
|
|
setPwForm({ currentPassword: "", newPassword: "", confirmPassword: "" });
|
|
},
|
|
onError: (e: any) => toast.error(e.response?.data?.message ?? e.message ?? "Failed to change password"),
|
|
});
|
|
|
|
return (
|
|
<div className="space-y-6 max-w-xl">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900">My Profile</h1>
|
|
<p className="text-sm text-gray-500 mt-1">Manage your account settings</p>
|
|
</div>
|
|
|
|
{/* Profile Info */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<User className="h-4 w-4 text-blue-600" />
|
|
Personal Information
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Input
|
|
label="First Name"
|
|
value={infoForm.firstName}
|
|
onChange={e => setInfoForm(f => ({ ...f, firstName: e.target.value }))}
|
|
/>
|
|
<Input
|
|
label="Last Name"
|
|
value={infoForm.lastName}
|
|
onChange={e => setInfoForm(f => ({ ...f, lastName: e.target.value }))}
|
|
/>
|
|
</div>
|
|
<Input
|
|
label="Email"
|
|
type="email"
|
|
value={infoForm.email}
|
|
onChange={e => setInfoForm(f => ({ ...f, email: e.target.value }))}
|
|
/>
|
|
<div className="flex justify-end">
|
|
<Button
|
|
onClick={() => updateInfo.mutate()}
|
|
isLoading={updateInfo.isPending}
|
|
disabled={!infoForm.firstName || !infoForm.email}
|
|
>
|
|
Save Changes
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Change Password */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Lock className="h-4 w-4 text-blue-600" />
|
|
Change Password
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<Input
|
|
label="Current Password"
|
|
type="password"
|
|
value={pwForm.currentPassword}
|
|
onChange={e => setPwForm(f => ({ ...f, currentPassword: e.target.value }))}
|
|
/>
|
|
<Input
|
|
label="New Password"
|
|
type="password"
|
|
value={pwForm.newPassword}
|
|
onChange={e => setPwForm(f => ({ ...f, newPassword: e.target.value }))}
|
|
hint="Minimum 8 characters"
|
|
/>
|
|
<Input
|
|
label="Confirm New Password"
|
|
type="password"
|
|
value={pwForm.confirmPassword}
|
|
onChange={e => setPwForm(f => ({ ...f, confirmPassword: e.target.value }))}
|
|
/>
|
|
{pwForm.newPassword && pwForm.confirmPassword && pwForm.newPassword !== pwForm.confirmPassword && (
|
|
<p className="text-xs text-red-500">Passwords do not match</p>
|
|
)}
|
|
<div className="flex justify-end">
|
|
<Button
|
|
onClick={() => resetPassword.mutate()}
|
|
isLoading={resetPassword.isPending}
|
|
disabled={!pwForm.currentPassword || !pwForm.newPassword || !pwForm.confirmPassword || pwForm.newPassword !== pwForm.confirmPassword}
|
|
>
|
|
Change Password
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|