import React, { useState, useEffect } from "react"; import { User, Lock, PawPrint, FileCheck, Plus, Archive } from "lucide-react"; import { PetForm } from "./PetForm.js"; import { authClient } from "../../lib/auth-client.js"; interface Props { sessionId: string | null; readOnly: boolean; } interface PersonalInfoData { id?: string; email?: string; firstName?: string; lastName?: string; phone?: string; address?: string; } interface PetData { id: string; name: string; species?: string; breed?: string; weight?: number; photo?: string; } export function AccountSettings({ sessionId, readOnly }: Props) { const [tab, setTab] = useState<"personal" | "password" | "pets" | "agreements">("personal"); return (
{([ { id: "personal" as const, label: "Personal Info", icon: User }, { id: "password" as const, label: "Password", icon: Lock }, { id: "pets" as const, label: "Manage Pets", icon: PawPrint }, { id: "agreements" as const, label: "Agreements", icon: FileCheck }, ]).map(({ id, label, icon: Icon }) => ( ))}
{tab === "personal" && } {tab === "password" && } {tab === "pets" && } {tab === "agreements" && }
); } function PersonalInfo({ sessionId, readOnly }: { sessionId: string | null; readOnly: boolean }) { const [form, setForm] = useState({ name: "", email: "", phone: "", address: "", }); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchPersonalInfo = async () => { try { setLoading(true); const response = await fetch("/api/portal/me", { headers: { "X-Impersonation-Session-Id": sessionId ?? "" }, }); if (response.ok) { const data: PersonalInfoData = await response.json(); setForm({ name: [data.firstName, data.lastName].filter(Boolean).join(" ") || "", email: data.email || "", phone: data.phone || "", address: data.address || "", }); } else { setError("Failed to load personal info"); } } catch { setError("Failed to load personal info"); } finally { setLoading(false); } }; fetchPersonalInfo(); }, [sessionId]); if (loading) { return (

Loading personal info...

); } if (error) { return (

{error}

); } return (

Personal Information

{([ { key: "name" as const, label: "Full Name", type: "text" }, { key: "email" as const, label: "Email", type: "email" }, { key: "phone" as const, label: "Phone", type: "tel" }, { key: "address" as const, label: "Address", type: "text" }, ]).map(({ key, label, type }) => (
!readOnly && setForm({ ...form, [key]: e.target.value })} disabled={readOnly} className="w-full border border-stone-300 rounded-lg px-3 py-2 text-sm disabled:bg-stone-50 disabled:text-stone-500" />
))} {!readOnly && ( )}
); } function PasswordChange({ readOnly }: { readOnly: boolean }) { const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const [loading, setLoading] = useState(false); const passwordsMatch = newPassword === confirmPassword; const canSubmit = newPassword.length > 0 && passwordsMatch && !loading; if (readOnly) { return (

Password changes are not available during staff impersonation.

); } async function handleSubmit() { if (!canSubmit) return; if (newPassword !== confirmPassword) { setError("Passwords do not match."); return; } setError(null); setLoading(true); try { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result = await (authClient as any).changePassword({ currentPassword, newPassword, }); if (result.error) { setError(result.error.message ?? "Failed to change password."); } else { setSuccess(true); setCurrentPassword(""); setNewPassword(""); setConfirmPassword(""); setTimeout(() => setSuccess(false), 4000); } } catch { setError("An unexpected error occurred."); } finally { setLoading(false); } } return (

Change Password

setCurrentPassword(e.target.value)} className="w-full border border-stone-300 rounded-lg px-3 py-2 text-sm" />
setNewPassword(e.target.value)} className="w-full border border-stone-300 rounded-lg px-3 py-2 text-sm" />
setConfirmPassword(e.target.value)} className="w-full border border-stone-300 rounded-lg px-3 py-2 text-sm" />
{error &&

{error}

} {success &&

Password updated successfully.

}
); } function ManagePets({ sessionId, readOnly }: { sessionId: string | null; readOnly: boolean }) { const [pets, setPets] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editingPetId, setEditingPetId] = useState(null); const [showAddForm, setShowAddForm] = useState(false); useEffect(() => { const fetchPets = async () => { try { setLoading(true); const response = await fetch("/api/portal/pets", { headers: { "X-Impersonation-Session-Id": sessionId ?? "" }, }); if (response.ok) { const data = await response.json(); setPets(Array.isArray(data) ? data : []); } else { setError("Failed to load pets"); } } catch { setError("Failed to load pets"); } finally { setLoading(false); } }; fetchPets(); }, [sessionId]); const editingPet = editingPetId ? pets.find(p => p.id === editingPetId) ?? undefined : undefined; if (loading) { return (

Loading pets...

); } if (error) { return (

{error}

); } if (editingPet || showAddForm) { return ( { setEditingPetId(null); setShowAddForm(false); }} onCancel={() => { setEditingPetId(null); setShowAddForm(false); }} /> ); } return (
{pets.map(pet => (
{pet.photo}

{pet.name}

{pet.breed} ยท {pet.weight} lbs

{!readOnly && (
)}
))} {!readOnly && ( )}
); } function Agreements() { return (

No agreements found. There is currently no agreements table in the database.

); }