import React, { useState, useEffect } from "react";
import { User, Lock, PawPrint, FileCheck, Plus, Archive } from "lucide-react";
import { PetForm } from "./PetForm.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 }) => (
setTab(id)}
className={`flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium ${
tab === id ? "bg-(--color-accent-light) text-(--color-accent-dark)" : "text-stone-500 hover:bg-stone-50"
}`}
>
{label}
))}
{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 (
);
}
if (error) {
return (
);
}
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 }) => (
{label}
!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 && (
Save Changes
)}
);
}
function PasswordChange({ readOnly }: { readOnly: boolean }) {
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState(null);
const passwordsMatch = newPassword === confirmPassword;
const canSubmit = currentPassword.length > 0 && newPassword.length > 0 && passwordsMatch;
if (readOnly) {
return (
Password changes are not available during staff impersonation.
);
}
function handleSubmit() {
if (!canSubmit) return;
if (newPassword !== confirmPassword) {
setError("Passwords do not match.");
return;
}
// TODO: Wire up to actual password-change API endpoint once backend support exists
setError(null);
setCurrentPassword("");
setNewPassword("");
setConfirmPassword("");
}
return (
);
}
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 (
);
}
if (error) {
return (
);
}
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 && (
setEditingPetId(pet.id)}
className="px-3 py-1.5 border border-stone-200 rounded-lg text-xs text-stone-600 hover:bg-stone-50"
>
Edit
)}
))}
{!readOnly && (
setShowAddForm(true)}
className="w-full flex items-center justify-center gap-2 py-3 border-2 border-dashed border-stone-300 rounded-2xl text-sm text-stone-500 hover:border-(--color-accent) hover:text-(--color-accent-dark) transition-colors"
>
Add New Pet
)}
);
}
function Agreements() {
return (
No agreements found. There is currently no agreements table in the database.
);
}