diff --git a/apps/web/src/portal/sections/AccountSettings.tsx b/apps/web/src/portal/sections/AccountSettings.tsx index 2fba3a6..973e7b5 100644 --- a/apps/web/src/portal/sections/AccountSettings.tsx +++ b/apps/web/src/portal/sections/AccountSettings.tsx @@ -142,6 +142,47 @@ function PersonalInfo({ sessionId, readOnly }: { sessionId: string | null; readO } function PasswordChange({ readOnly }: { readOnly: boolean }) { + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [validationError, setValidationError] = useState(null); + const [submitting, setSubmitting] = useState(false); + + const passwordsMatch = newPassword === confirmPassword; + const canSubmit = + !readOnly && + currentPassword.length > 0 && + newPassword.length > 0 && + confirmPassword.length > 0 && + passwordsMatch; + + const handleSubmit = async () => { + setValidationError(null); + + if (!passwordsMatch) { + setValidationError("Passwords do not match."); + return; + } + + if (newPassword.length === 0 || confirmPassword.length === 0) { + setValidationError("All fields are required."); + return; + } + + setSubmitting(true); + + // TODO: wire up to actual password-change API endpoint (e.g., POST /api/portal/password) + // const response = await fetch("/api/portal/password", { ... }); + + setTimeout(() => { + setSubmitting(false); + setCurrentPassword(""); + setNewPassword(""); + setConfirmPassword(""); + // On success, clear error and show confirmation (API integration needed first) + }, 500); + }; + if (readOnly) { return (
@@ -156,18 +197,44 @@ function PasswordChange({ readOnly }: { readOnly: boolean }) {
- + setCurrentPassword(e.target.value)} + className="w-full border border-stone-300 rounded-lg px-3 py-2 text-sm" + />
- + { setNewPassword(e.target.value); setValidationError(null); }} + className="w-full border border-stone-300 rounded-lg px-3 py-2 text-sm" + />
- + { setConfirmPassword(e.target.value); setValidationError(null); }} + className="w-full border border-stone-300 rounded-lg px-3 py-2 text-sm" + />
-
diff --git a/apps/web/src/portal/sections/ReportCards.tsx b/apps/web/src/portal/sections/ReportCards.tsx index a8d471b..5c8a509 100644 --- a/apps/web/src/portal/sections/ReportCards.tsx +++ b/apps/web/src/portal/sections/ReportCards.tsx @@ -30,29 +30,31 @@ export function ReportCards() { const [error, setError] = useState(null); const [selectedCard, setSelectedCard] = useState(null); - useEffect(() => { - const fetchReportCards = async () => { - try { - const response = await fetch("/api/portal/appointments"); + const loadReportCards = async () => { + try { + setError(null); + setIsLoading(true); + const response = await fetch("/api/portal/appointments"); - if (response.ok) { - const data = await response.json(); - const allAppointments: Appointment[] = data.appointments || data || []; - const reportCardAppointments = allAppointments.filter( - (appt) => appt.reportCardId - ); - setAppointments(reportCardAppointments); - } else { - setError("Failed to load report cards."); - } - } catch { - setError("Failed to load report cards. Please try again."); - } finally { - setIsLoading(false); + if (response.ok) { + const data = await response.json(); + const allAppointments: Appointment[] = data.appointments || data || []; + const reportCardAppointments = allAppointments.filter( + (appt) => appt.reportCardId + ); + setAppointments(reportCardAppointments); + } else { + setError("Failed to load report cards."); } - }; + } catch { + setError("Failed to load report cards. Please try again."); + } finally { + setIsLoading(false); + } + }; - fetchReportCards(); + useEffect(() => { + loadReportCards(); }, []); if (isLoading) { @@ -69,7 +71,7 @@ export function ReportCards() {

{error}