feat: web sprint 250-262 — plans/clients/tickets/audit/profile/settings/leads/reports #26
147
app/(app)/profile/page.tsx
Normal file
147
app/(app)/profile/page.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
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 } = useAuth();
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user