fix(settings): pediatrician save + edit mode

- API: dynamic SET clause (only updates fields present in body) fixes
  undefined param bug and allows clearing fields; replaces blanket COALESCE
- Settings UI: display/edit toggle — saved details shown with Edit button,
  inputs open on first visit or when editing; Save shows inline error on
  failure and brief "Saved!" on success

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-28 01:00:00 +05:30
parent 0a9def36bf
commit bdb5199d5f
2 changed files with 90 additions and 23 deletions

View file

@ -41,6 +41,9 @@ export default function SettingsPage() {
const [pedPhone, setPedPhone] = useState("");
const [pedName, setPedName] = useState("");
const [pedSaving, setPedSaving] = useState(false);
const [pedEditing, setPedEditing] = useState(false);
const [pedSaved, setPedSaved] = useState(false);
const [pedError, setPedError] = useState("");
// Check if can invite more members (client-side pre-check; server enforces)
const canInvite = tier === "pro" || memberCount < 2;
@ -60,8 +63,10 @@ export default function SettingsPage() {
fetchMembers();
fetchInvites();
fetch("/api/family").then(r => r.json()).then(d => {
if (d.family?.pediatrician_phone) setPedPhone(d.family.pediatrician_phone);
if (d.family?.pediatrician_name) setPedName(d.family.pediatrician_name);
setPedPhone(d.family?.pediatrician_phone || "");
setPedName(d.family?.pediatrician_name || "");
// If no data yet, open in edit mode so user can fill it in
if (!d.family?.pediatrician_phone && !d.family?.pediatrician_name) setPedEditing(true);
}).catch(() => {});
}
}, [familyId]);
@ -96,11 +101,24 @@ export default function SettingsPage() {
const savePedInfo = async () => {
setPedSaving(true);
await fetch("/api/family", {
setPedError("");
try {
const res = await fetch("/api/family", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pediatricianPhone: pedPhone, pediatricianName: pedName }),
}).catch(() => {});
});
const data = await res.json();
if (!res.ok) {
setPedError(data.error || "Failed to save. Please try again.");
} else {
setPedEditing(false);
setPedSaved(true);
setTimeout(() => setPedSaved(false), 3000);
}
} catch {
setPedError("Network error. Please try again.");
}
setPedSaving(false);
};
@ -360,10 +378,31 @@ export default function SettingsPage() {
{/* Pediatrician */}
<div className="bg-white dark:bg-gray-800 rounded-xl p-4 space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xl">🏥</span>
<div className="font-medium dark:text-white">Pediatrician</div>
</div>
{!pedEditing && (pedName || pedPhone) && (
<button
onClick={() => { setPedEditing(true); setPedSaved(false); setPedError(""); }}
className="text-sm text-rose-500 dark:text-rose-400 font-medium"
>
Edit
</button>
)}
</div>
{/* Display mode */}
{!pedEditing && (pedName || pedPhone) ? (
<div className="space-y-0.5">
{pedName && <div className="text-sm font-medium text-gray-800 dark:text-gray-100">{pedName}</div>}
{pedPhone && <div className="text-sm text-gray-500 dark:text-gray-400">{pedPhone}</div>}
{pedSaved && <div className="text-xs text-green-600 dark:text-green-400">Saved!</div>}
</div>
) : (
/* Edit / first-fill mode */
<div className="space-y-2">
<p className="text-xs text-gray-400 dark:text-gray-500">Shown on the emergency guide and in AI medical redirects.</p>
<Input
type="text"
@ -375,6 +414,18 @@ export default function SettingsPage() {
<Input type="tel" value={pedPhone} onChange={e => setPedPhone(e.target.value)} placeholder="+91 98765 43210" className="flex-1" />
<Button size="sm" loading={pedSaving} onClick={savePedInfo}>Save</Button>
</div>
{pedEditing && (pedName || pedPhone) && (
<button
onClick={() => { setPedEditing(false); setPedError(""); }}
className="text-xs text-gray-400 dark:text-gray-500"
>
Cancel
</button>
)}
{pedError && <p className="text-xs text-red-500">{pedError}</p>}
</div>
)}
<Link href="/medical/emergency" className="text-xs text-rose-500 dark:text-rose-400">
View Emergency Guide
</Link>

View file

@ -33,9 +33,25 @@ export async function PATCH(request: Request) {
const body = await request.json();
const { name, pediatricianPhone, pediatricianName, tier } = body;
// Only update fields that are explicitly present in the request body.
// This avoids COALESCE masking clears and undefined params causing errors.
const setClauses: string[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const params: any[] = [];
if (name !== undefined) { setClauses.push(`name = $${params.push(name)}`); }
if (pediatricianPhone !== undefined) { setClauses.push(`pediatrician_phone = $${params.push(pediatricianPhone || null)}`); }
if (pediatricianName !== undefined) { setClauses.push(`pediatrician_name = $${params.push(pediatricianName || null)}`); }
if (tier !== undefined) { setClauses.push(`tier = $${params.push(tier)}`); }
if (setClauses.length === 0) return NextResponse.json({ success: true });
setClauses.push("updated_at = NOW()");
params.push(auth.session!.familyId);
await sql.unsafe(
`UPDATE families SET name = COALESCE($1, name), pediatrician_phone = COALESCE($2, pediatrician_phone), pediatrician_name = COALESCE($3, pediatrician_name), tier = COALESCE($4, tier), updated_at = NOW() WHERE id = $5`,
[name, pediatricianPhone, pediatricianName, tier, auth.session!.familyId]
`UPDATE families SET ${setClauses.join(", ")} WHERE id = $${params.length}`,
params
);
return NextResponse.json({ success: true });