"use client"; import { useEffect, useState, useRef } from "react"; import { getPasswordRequirements, PasswordRequirement } from "../../../lib/auth/validation"; export default function SettingsClient() { const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [msg, setMsg] = useState(null); const [uploading, setUploading] = useState(false); const [passwordRequirements, setPasswordRequirements] = useState([]); const fileInputRef = useRef(null); const getMinDate = (): string => { const date = new Date(); date.setFullYear(date.getFullYear() - 100); return date.toISOString().split('T')[0]; }; const getMaxDate = (): string => { const date = new Date(); date.setFullYear(date.getFullYear() - 18); return date.toISOString().split('T')[0]; }; const [profile, setProfile] = useState({ firstName: "", lastName: "", gender: "", dob: "", address: "", avatarUrl: "", email: "", }); useEffect(() => { fetch("/api/account/profile") .then((r) => r.json()) .then((d) => d.ok && setProfile(d.profile)) .catch(() => null); }, []); useEffect(() => { setPasswordRequirements(getPasswordRequirements(newPassword)); }, [newPassword]); async function submit() { setMsg(null); // Client-side validation if (!currentPassword.trim()) { setMsg("Current password is required"); return; } // Validate new password meets all requirements const unmetRequirements = passwordRequirements.filter(req => !req.met); if (unmetRequirements.length > 0) { const messages = unmetRequirements.map(req => req.name).join(", "); setMsg(`Password requirements not met: ${messages}`); return; } if (newPassword !== confirmPassword) { setMsg("Passwords do not match"); return; } const res = await fetch("/api/auth/change-password", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ currentPassword, newPassword, confirmPassword }), }); const data = await res.json(); if (data.ok) { setCurrentPassword(""); setNewPassword(""); setConfirmPassword(""); } setMsg(data.ok ? "✅ Password updated." : data.message); } async function saveProfile() { setMsg(null); const res = await fetch("/api/account/profile", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(profile), }); const data = await res.json(); setMsg(data.ok ? "✅ Profile updated." : data.message); // Reload profile data to show updated avatar if (data.ok) { const profileRes = await fetch("/api/account/profile"); const profileData = await profileRes.json(); if (profileData.ok) { setProfile(profileData.profile); } // Trigger navbar refresh by dispatching custom event window.dispatchEvent(new Event('profile-updated')); } } async function handleFileChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; // Validate file type if (!file.type.startsWith("image/")) { setMsg("Please select an image file"); return; } // Validate file size (max 5MB) if (file.size > 5 * 1024 * 1024) { setMsg("Image size must be less than 5MB"); return; } setUploading(true); setMsg(null); // Convert to base64 const reader = new FileReader(); reader.onloadend = () => { const base64 = reader.result as string; setProfile({ ...profile, avatarUrl: base64 }); setUploading(false); setMsg("✅ Image loaded. Click 'Save Profile' to update."); }; reader.onerror = () => { setUploading(false); setMsg("Failed to read image file"); }; reader.readAsDataURL(file); } return (
👤

Profile Information

Update your personal details

{/* Profile Photo Section */}
{profile.avatarUrl ? ( // eslint-disable-next-line @next/next/no-img-element Profile ) : (
{profile.firstName?.[0]?.toUpperCase() || "U"}
)} {uploading && (
)}

Profile Photo

JPG, PNG or GIF. Max size 5MB.

{profile.avatarUrl && ( )}
setProfile({ ...profile, firstName: e.target.value })} />
setProfile({ ...profile, lastName: e.target.value })} />

Email cannot be changed

setProfile({ ...profile, dob: e.target.value })} />