From defbf589ac1581c1c8bca4baf053a1bd00ec9596 Mon Sep 17 00:00:00 2001 From: Berny-ft Date: Sun, 19 Jul 2026 12:00:56 -0400 Subject: [PATCH 1/7] feat(user-actions): add Manage Children button and modal to user actions cell --- .../children/child-detail-dialog.tsx | 370 ++++++++++++++++++ .../children/create-child-dialog.tsx | 349 +++++++++++++++++ .../users/_components/children/dob-field.tsx | 90 +++++ .../children/emergency-contact-dialog.tsx | 158 ++++++++ .../children/manage-children-modal.tsx | 194 +++++++++ .../users/_components/user-actions-cell.tsx | 23 +- app/(authenticated)/users/children-actions.ts | 227 +++++++++++ app/(authenticated)/users/children-queries.ts | 65 +++ app/(authenticated)/users/children-schema.ts | 32 ++ 9 files changed, 1507 insertions(+), 1 deletion(-) create mode 100644 app/(authenticated)/users/_components/children/child-detail-dialog.tsx create mode 100644 app/(authenticated)/users/_components/children/create-child-dialog.tsx create mode 100644 app/(authenticated)/users/_components/children/dob-field.tsx create mode 100644 app/(authenticated)/users/_components/children/emergency-contact-dialog.tsx create mode 100644 app/(authenticated)/users/_components/children/manage-children-modal.tsx create mode 100644 app/(authenticated)/users/children-actions.ts create mode 100644 app/(authenticated)/users/children-queries.ts create mode 100644 app/(authenticated)/users/children-schema.ts diff --git a/app/(authenticated)/users/_components/children/child-detail-dialog.tsx b/app/(authenticated)/users/_components/children/child-detail-dialog.tsx new file mode 100644 index 0000000..9560fda --- /dev/null +++ b/app/(authenticated)/users/_components/children/child-detail-dialog.tsx @@ -0,0 +1,370 @@ +"use client"; + +import { useActionState, useCallback, useState } from "react"; +import { ChevronDown, Loader2, Plus, Trash2 } from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { cn } from "@/lib/utils"; +import { + updateChildAdmin, + type ChildActionState, +} from "@/app/(authenticated)/users/children-actions"; +import type { ChildView } from "@/app/(authenticated)/users/children-queries"; + +import { DobField } from "./dob-field"; +import { + EmergencyContactDialog, + type DraftEmergencyContact, +} from "./emergency-contact-dialog"; + +function FieldError({ errors }: { errors?: string[] }) { + if (!errors?.length) return null; + return

{errors[0]}

; +} + +function toDraftContacts(child: ChildView): DraftEmergencyContact[] { + return child.emergencyContacts.map((c) => ({ + id: c.id, + full_name: c.fullName, + email_address: c.emailAddress, + phone_number: c.phoneNumber, + relationship: c.relationship, + })); +} + +function ChildEditForm({ + child, + parentId, + onClose, + onSuccess, +}: { + child: ChildView; + parentId: string; + onClose: () => void; + onSuccess?: () => void; +}) { + const [gender, setGender] = useState(child.gender); + const [dob, setDob] = useState(child.dob); + const [emergencyContacts, setEmergencyContacts] = useState(() => + toDraftContacts(child), + ); + const [ecSectionOpen, setEcSectionOpen] = useState(true); + const [addContactOpen, setAddContactOpen] = useState(false); + + const boundFormAction = useCallback( + async (prev: ChildActionState, formData: FormData) => { + if (emergencyContacts.length === 0) { + return { + errors: { + emergency_contacts: ["Add at least one emergency contact"], + }, + }; + } + formData.set("child_id", child.id); + formData.set("parent_id", parentId); + formData.set( + "emergency_contacts", + JSON.stringify( + emergencyContacts.map( + ({ + full_name, + email_address, + phone_number, + relationship, + }) => ({ + full_name, + email_address, + phone_number, + relationship, + }), + ), + ), + ); + const result = await updateChildAdmin(prev, formData); + if (result?.message && !result.errors) { + toast.success(result.message); + onClose(); + onSuccess?.(); + } else if (result?.errors?._form?.[0]) { + toast.error(result.errors._form[0]); + } + return result; + }, + [child.id, parentId, emergencyContacts, onClose, onSuccess], + ); + + const [state, formAction, pending] = useActionState< + ChildActionState, + FormData + >(boundFormAction, null); + + function handleAddContact(contact: Omit) { + setEmergencyContacts((prev) => [ + ...prev, + { ...contact, id: crypto.randomUUID() }, + ]); + setEcSectionOpen(true); + } + + function handleRemoveContact(id: string) { + setEmergencyContacts((prev) => prev.filter((c) => c.id !== id)); + } + + return ( + <> + + + + {child.firstName} {child.lastName} + + + Update this child's profile and emergency contacts. + + + +
+
+ {state?.errors?._form?.map((msg) => ( +

+ {msg} +

+ ))} + +
+
+ + + +
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+ +