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 (
);
}
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 }) => (
!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 (
);
}
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 && (
)}
))}
{!readOnly && (
)}
);
}
function Agreements() {
return (
No agreements found. There is currently no agreements table in the database.
);
}