From df3fcbf8853ed033736aba817bb134cb499cd446 Mon Sep 17 00:00:00 2001 From: blaisadmin Date: Thu, 21 May 2026 17:27:57 -0400 Subject: [PATCH] automated dev db build --- backend/src/app.ts | 14 ++- frontend/src/App.tsx | 201 ++++++++++++++++++++++++----------------- frontend/src/index.css | 49 +++++++++- 3 files changed, 177 insertions(+), 87 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 1ded93f..f8b8145 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -234,13 +234,23 @@ const lostBirdReportSchema = z.object({ }); const publicProfileCodeSchema = z.string().trim().regex(/^[A-Za-z0-9_-]{8,32}$/); +const birdProfileListSchema = z + .string() + .trim() + .max(1000) + .refine( + (value) => value.split(/\r?\n/).map((item) => item.trim()).filter(Boolean).length <= 3, + 'Use no more than three list items.', + ) + .optional() + .or(z.literal('')); const birdSchema = z.object({ name: z.string().trim().min(1).max(120), tagId: z.string().trim().max(80).optional().or(z.literal('')), species: z.string().trim().min(1).max(120), - motivators: z.string().trim().max(1000).optional().or(z.literal('')), - demotivators: z.string().trim().max(1000).optional().or(z.literal('')), + motivators: birdProfileListSchema, + demotivators: birdProfileListSchema, favoriteSnack: z.string().trim().max(160).optional().or(z.literal('')), gender: birdGenderSchema.optional(), dateOfBirth: dateStringSchema.optional().or(z.literal('')), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b58ff34..580f2b7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -472,6 +472,23 @@ const parseImportGender = (value: unknown): BirdGender | null => { const getBirdImportKey = (name: string, tagId: string) => (tagId ? `band:${tagId.toLowerCase()}` : `name:${name.toLowerCase()}`); const mergeImportText = (current: string, next: string) => current || next; +const birdProfileListLimit = 3; + +const parseBirdProfileList = (value: string | null | undefined) => + (value ?? '') + .split(/\r?\n/) + .map((item) => item.trim()) + .filter(Boolean) + .slice(0, birdProfileListLimit); + +const getBirdProfileListFields = (value: string) => + Array.from({ length: birdProfileListLimit }, (_, index) => parseBirdProfileList(value)[index] ?? ''); + +const updateBirdProfileListField = (value: string, index: number, nextItem: string) => { + const items = getBirdProfileListFields(value); + items[index] = nextItem; + return items.map((item) => item.trim()).filter(Boolean).join('\n'); +}; const parseBirdImportRows = (rows: Record[]): BirdImportPreview => { const errors: string[] = []; @@ -701,8 +718,8 @@ const toBirdForm = (bird: Bird): BirdFormState => ({ name: bird.name, tagId: bird.tagId ?? '', species: bird.species, - motivators: bird.motivators ?? '', - demotivators: bird.demotivators ?? '', + motivators: parseBirdProfileList(bird.motivators).join('\n'), + demotivators: parseBirdProfileList(bird.demotivators).join('\n'), favoriteSnack: bird.favoriteSnack ?? '', gender: bird.gender, dateOfBirth: bird.dateOfBirth ?? '', @@ -4804,26 +4821,33 @@ function App() { Gotcha day {formatDate(selectedBird.gotchaDay)} -
- Gender - - - {getBirdGenderLabel(selectedBird)} - -
Favorite snack {selectedBird.favoriteSnack || 'Not recorded'}
Motivators - {selectedBird.motivators || 'Not recorded'} + {parseBirdProfileList(selectedBird.motivators).length ? ( +
    + {parseBirdProfileList(selectedBird.motivators).map((motivator, index) => ( +
  • {motivator}
  • + ))} +
+ ) : ( + Not recorded + )}
Demotivators - {selectedBird.demotivators || 'Not recorded'} + {parseBirdProfileList(selectedBird.demotivators).length ? ( +
    + {parseBirdProfileList(selectedBird.demotivators).map((demotivator, index) => ( +
  • {demotivator}
  • + ))} +
+ ) : ( + Not recorded + )}
@@ -5577,7 +5601,7 @@ function App() { ) : null}
- {workspace?.billingEmail || authSession.user.email} + {workspace?.billingEmail || authSession.user.email} Billing contact for invoices, receipts, and account notices.
@@ -5895,30 +5919,6 @@ function App() { Search or select a species so alerts and chart references stay consistent. - -