Added vet info
Deploy / deploy-dev (push) Successful in 3m0s
Deploy / deploy-prod (push) Has been skipped

This commit is contained in:
Corey Blais
2026-06-01 14:59:03 -04:00
parent 6ade13a8be
commit 3053e3bef5
6 changed files with 350 additions and 21 deletions
+16
View File
@@ -276,6 +276,10 @@ const birdSchema = z.object({
motivators: birdProfileListSchema,
demotivators: birdProfileListSchema,
favoriteSnack: z.string().trim().max(160).optional().or(z.literal('')),
vetClinicName: z.string().trim().max(160).optional().or(z.literal('')),
vetClinicAddress: z.string().trim().max(500).optional().or(z.literal('')),
vetAccountNumber: z.string().trim().max(120).optional().or(z.literal('')),
vetDoctorName: z.string().trim().max(160).optional().or(z.literal('')),
gender: birdGenderSchema.optional(),
dateOfBirth: dateStringSchema.optional().or(z.literal('')),
gotchaDay: dateStringSchema.optional().or(z.literal('')),
@@ -671,6 +675,10 @@ const normalizeBird = (row: BirdRow) => ({
motivators: row.motivators,
demotivators: row.demotivators,
favoriteSnack: row.favorite_snack,
vetClinicName: row.vet_clinic_name,
vetClinicAddress: row.vet_clinic_address,
vetAccountNumber: row.vet_account_number,
vetDoctorName: row.vet_doctor_name,
gender: row.gender,
dateOfBirth: row.date_of_birth,
gotchaDay: row.gotcha_day,
@@ -3278,6 +3286,10 @@ app.post('/api/birds', requireAuth, requireWriteAccess, requireWorkspaceRole(['o
motivators: emptyToNull(parsed.data.motivators),
demotivators: emptyToNull(parsed.data.demotivators),
favoriteSnack: emptyToNull(parsed.data.favoriteSnack),
vetClinicName: emptyToNull(parsed.data.vetClinicName),
vetClinicAddress: emptyToNull(parsed.data.vetClinicAddress),
vetAccountNumber: emptyToNull(parsed.data.vetAccountNumber),
vetDoctorName: emptyToNull(parsed.data.vetDoctorName),
gender: (parsed.data.gender ?? 'unknown') as BirdGender,
dateOfBirth: emptyToNull(parsed.data.dateOfBirth),
gotchaDay: emptyToNull(parsed.data.gotchaDay),
@@ -3431,6 +3443,10 @@ app.put('/api/birds/:birdId', requireAuth, requireWriteAccess, requireWorkspaceR
motivators: emptyToNull(parsed.data.motivators),
demotivators: emptyToNull(parsed.data.demotivators),
favoriteSnack: emptyToNull(parsed.data.favoriteSnack),
vetClinicName: emptyToNull(parsed.data.vetClinicName),
vetClinicAddress: emptyToNull(parsed.data.vetClinicAddress),
vetAccountNumber: emptyToNull(parsed.data.vetAccountNumber),
vetDoctorName: emptyToNull(parsed.data.vetDoctorName),
gender: (parsed.data.gender ?? 'unknown') as BirdGender,
dateOfBirth: emptyToNull(parsed.data.dateOfBirth),
gotchaDay: emptyToNull(parsed.data.gotchaDay),
+8
View File
@@ -249,6 +249,10 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
motivators VARCHAR(1000),
demotivators VARCHAR(1000),
favorite_snack VARCHAR(160),
vet_clinic_name VARCHAR(160),
vet_clinic_address VARCHAR(500),
vet_account_number VARCHAR(120),
vet_doctor_name VARCHAR(160),
gender VARCHAR(16) NOT NULL DEFAULT 'unknown',
date_of_birth DATE,
gotcha_day DATE,
@@ -273,6 +277,10 @@ export const ensureSchema = async (database: DatabaseClient = db) => {
ADD COLUMN IF NOT EXISTS motivators VARCHAR(1000),
ADD COLUMN IF NOT EXISTS demotivators VARCHAR(1000),
ADD COLUMN IF NOT EXISTS favorite_snack VARCHAR(160),
ADD COLUMN IF NOT EXISTS vet_clinic_name VARCHAR(160),
ADD COLUMN IF NOT EXISTS vet_clinic_address VARCHAR(500),
ADD COLUMN IF NOT EXISTS vet_account_number VARCHAR(120),
ADD COLUMN IF NOT EXISTS vet_doctor_name VARCHAR(160),
ADD COLUMN IF NOT EXISTS gender VARCHAR(16) NOT NULL DEFAULT 'unknown',
ADD COLUMN IF NOT EXISTS date_of_birth DATE,
ADD COLUMN IF NOT EXISTS gotcha_day DATE,
+52 -20
View File
@@ -23,6 +23,10 @@ const birdSelectFields = `
birds.motivators,
birds.demotivators,
birds.favorite_snack,
birds.vet_clinic_name,
birds.vet_clinic_address,
birds.vet_account_number,
birds.vet_doctor_name,
birds.gender,
birds.date_of_birth::text,
birds.gotcha_day::text,
@@ -287,6 +291,10 @@ export const createBird = async ({
motivators,
demotivators,
favoriteSnack,
vetClinicName = null,
vetClinicAddress = null,
vetAccountNumber = null,
vetDoctorName = null,
gender,
dateOfBirth,
gotchaDay,
@@ -308,6 +316,10 @@ export const createBird = async ({
motivators: string | null;
demotivators: string | null;
favoriteSnack: string | null;
vetClinicName?: string | null;
vetClinicAddress?: string | null;
vetAccountNumber?: string | null;
vetDoctorName?: string | null;
gender: BirdGender;
dateOfBirth: string | null;
gotchaDay: string | null;
@@ -322,9 +334,9 @@ export const createBird = async ({
publicProfileEnabled?: boolean;
}) => {
const result = await db.query<BirdRow>(
`INSERT INTO birds (id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, gender, date_of_birth, gotcha_day, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled)
VALUES (COALESCE($1::uuid, gen_random_uuid()), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at, NULL::text AS latest_weight_grams, NULL::text AS latest_recorded_on`,
`INSERT INTO birds (id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth, gotcha_day, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled)
VALUES (COALESCE($1::uuid, gen_random_uuid()), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at, NULL::text AS latest_weight_grams, NULL::text AS latest_recorded_on`,
[
birdId ?? null,
workspaceId,
@@ -334,6 +346,10 @@ export const createBird = async ({
motivators,
demotivators,
favoriteSnack,
vetClinicName,
vetClinicAddress,
vetAccountNumber,
vetDoctorName,
gender,
dateOfBirth,
gotchaDay,
@@ -361,6 +377,10 @@ export const updateBird = async ({
motivators,
demotivators,
favoriteSnack,
vetClinicName,
vetClinicAddress,
vetAccountNumber,
vetDoctorName,
gender,
dateOfBirth,
gotchaDay,
@@ -382,6 +402,10 @@ export const updateBird = async ({
motivators: string | null;
demotivators: string | null;
favoriteSnack: string | null;
vetClinicName: string | null;
vetClinicAddress: string | null;
vetAccountNumber: string | null;
vetDoctorName: string | null;
gender: BirdGender;
dateOfBirth: string | null;
gotchaDay: string | null;
@@ -403,22 +427,26 @@ export const updateBird = async ({
motivators = $5,
demotivators = $6,
favorite_snack = $7,
gender = $8,
date_of_birth = $9,
gotcha_day = $10,
chart_color = $11,
photo_data_url = $12,
photo_object_key = $13,
photo_content_type = $14,
photo_updated_at = $15,
notify_on_dob = $16,
notify_on_gotcha_day = $17,
public_profile_code = $18,
public_profile_enabled = $19
vet_clinic_name = $8,
vet_clinic_address = $9,
vet_account_number = $10,
vet_doctor_name = $11,
gender = $12,
date_of_birth = $13,
gotcha_day = $14,
chart_color = $15,
photo_data_url = $16,
photo_object_key = $17,
photo_content_type = $18,
photo_updated_at = $19,
notify_on_dob = $20,
notify_on_gotcha_day = $21,
public_profile_code = $22,
public_profile_enabled = $23
WHERE id = $1
AND workspace_id = $20
AND workspace_id = $24
AND memorialized_at IS NULL
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
(
SELECT weight_grams::text
FROM weight_records
@@ -441,6 +469,10 @@ export const updateBird = async ({
motivators,
demotivators,
favoriteSnack,
vetClinicName,
vetClinicAddress,
vetAccountNumber,
vetDoctorName,
gender,
dateOfBirth,
gotchaDay,
@@ -482,7 +514,7 @@ export const memorializeBird = async ({
WHERE id = $1
AND workspace_id = $2
AND memorialized_at IS NULL
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
(
SELECT weight_grams::text
FROM weight_records
@@ -518,7 +550,7 @@ export const updateMemorialReminderPreference = async ({
WHERE id = $1
AND workspace_id = $2
AND memorialized_at IS NOT NULL
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
(
SELECT weight_grams::text
FROM weight_records
@@ -558,7 +590,7 @@ export const transferBirdToWorkspace = async (birdId: string, sourceWorkspaceId:
WHERE id = $1
AND workspace_id = $2
AND memorialized_at IS NULL
RETURNING id, workspace_id, name, tag_id, species, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
RETURNING id, workspace_id, name, tag_id, species, motivators, demotivators, favorite_snack, vet_clinic_name, vet_clinic_address, vet_account_number, vet_doctor_name, gender, date_of_birth::text, gotcha_day::text, chart_color, photo_data_url, photo_object_key, photo_content_type, photo_updated_at, notify_on_dob, notify_on_gotcha_day, public_profile_code, public_profile_enabled, memorialized_at, memorialized_on::text, memorial_note, notify_on_memorial_day, created_at,
(
SELECT weight_grams::text
FROM weight_records
+4
View File
@@ -130,6 +130,10 @@ export type BirdRow = {
motivators: string | null;
demotivators: string | null;
favorite_snack: string | null;
vet_clinic_name: string | null;
vet_clinic_address: string | null;
vet_account_number: string | null;
vet_doctor_name: string | null;
gender: BirdGender;
date_of_birth: string | null;
gotcha_day: string | null;
+9 -1
View File
@@ -208,6 +208,10 @@ Role requirements are called out per endpoint below. If the signed-in member lac
"name": "Kiwi",
"tagId": "FP-001",
"species": "Cockatiel",
"vetClinicName": "Avian Care Center",
"vetClinicAddress": "123 Feather Lane, Raleigh, NC",
"vetAccountNumber": "FP-1001",
"vetDoctorName": "Dr. Rivera",
"gender": "female",
"dateOfBirth": "2023-05-10",
"gotchaDay": "2023-08-21",
@@ -793,6 +797,10 @@ Request body:
"name": "Kiwi",
"tagId": "FP-001",
"species": "Cockatiel",
"vetClinicName": "Avian Care Center",
"vetClinicAddress": "123 Feather Lane, Raleigh, NC",
"vetAccountNumber": "FP-1001",
"vetDoctorName": "Dr. Rivera",
"gender": "female",
"dateOfBirth": "2023-05-10",
"gotchaDay": "2023-08-21",
@@ -805,7 +813,7 @@ Request body:
Notes:
- `dateOfBirth`, `gotchaDay`, and `photoDataUrl` may be omitted or sent as empty strings
- `dateOfBirth`, `gotchaDay`, `photoDataUrl`, and veterinary info fields may be omitted or sent as empty strings
- `chartColor` defaults to `#cb3a35`
Response `201`:
+261
View File
@@ -24,6 +24,10 @@ type Bird = {
motivators: string | null;
demotivators: string | null;
favoriteSnack: string | null;
vetClinicName: string | null;
vetClinicAddress: string | null;
vetAccountNumber: string | null;
vetDoctorName: string | null;
gender: BirdGender;
dateOfBirth: string | null;
gotchaDay: string | null;
@@ -236,6 +240,10 @@ type BirdFormState = {
motivators: string;
demotivators: string;
favoriteSnack: string;
vetClinicName: string;
vetClinicAddress: string;
vetAccountNumber: string;
vetDoctorName: string;
gender: BirdGender;
dateOfBirth: string;
gotchaDay: string;
@@ -246,6 +254,8 @@ type BirdFormState = {
publicProfileEnabled: boolean;
};
type VeterinaryInfoFormState = Pick<BirdFormState, 'vetClinicName' | 'vetClinicAddress' | 'vetAccountNumber' | 'vetDoctorName'>;
type BirdImportWeight = {
weightGrams: number;
recordedOn: string;
@@ -656,6 +666,10 @@ const emptyBirdForm: BirdFormState = {
motivators: '',
demotivators: '',
favoriteSnack: '',
vetClinicName: '',
vetClinicAddress: '',
vetAccountNumber: '',
vetDoctorName: '',
gender: 'unknown',
dateOfBirth: '',
gotchaDay: '',
@@ -666,6 +680,13 @@ const emptyBirdForm: BirdFormState = {
publicProfileEnabled: false,
};
const emptyVeterinaryInfoForm: VeterinaryInfoFormState = {
vetClinicName: '',
vetClinicAddress: '',
vetAccountNumber: '',
vetDoctorName: '',
};
const emptyMemorializeBirdForm = (): MemorializeBirdFormState => ({
memorializedOn: new Date().toISOString().slice(0, 10),
memorialNote: '',
@@ -799,6 +820,10 @@ const toBirdForm = (bird: Bird): BirdFormState => ({
motivators: parseBirdProfileList(bird.motivators).join('\n'),
demotivators: parseBirdProfileList(bird.demotivators).join('\n'),
favoriteSnack: bird.favoriteSnack ?? '',
vetClinicName: bird.vetClinicName ?? '',
vetClinicAddress: bird.vetClinicAddress ?? '',
vetAccountNumber: bird.vetAccountNumber ?? '',
vetDoctorName: bird.vetDoctorName ?? '',
gender: bird.gender,
dateOfBirth: bird.dateOfBirth ?? '',
gotchaDay: bird.gotchaDay ?? '',
@@ -809,6 +834,13 @@ const toBirdForm = (bird: Bird): BirdFormState => ({
publicProfileEnabled: bird.publicProfileEnabled,
});
const toVeterinaryInfoForm = (bird: Bird): VeterinaryInfoFormState => ({
vetClinicName: bird.vetClinicName ?? '',
vetClinicAddress: bird.vetClinicAddress ?? '',
vetAccountNumber: bird.vetAccountNumber ?? '',
vetDoctorName: bird.vetDoctorName ?? '',
});
const formatDate = (value: string | null) => {
if (!value) {
return 'Not set';
@@ -1593,6 +1625,7 @@ function App() {
reason: '',
notes: '',
});
const [veterinaryInfoForm, setVeterinaryInfoForm] = useState<VeterinaryInfoFormState>(emptyVeterinaryInfoForm);
const [medicationForm, setMedicationForm] = useState({
name: '',
dosage: '',
@@ -1617,6 +1650,8 @@ function App() {
const [memorializingBird, setMemorializingBird] = useState(false);
const [savingMemorialReminderBirdId, setSavingMemorialReminderBirdId] = useState('');
const [editingVetVisitId, setEditingVetVisitId] = useState('');
const [editingVeterinaryInfo, setEditingVeterinaryInfo] = useState(false);
const [savingVeterinaryInfo, setSavingVeterinaryInfo] = useState(false);
const [deletingVetVisitId, setDeletingVetVisitId] = useState('');
const [editingMedicationId, setEditingMedicationId] = useState('');
const [deletingMedicationId, setDeletingMedicationId] = useState('');
@@ -1657,6 +1692,15 @@ function App() {
setSelectedBirdTab('info');
}, [selectedBirdId]);
useEffect(() => {
if (selectedBird) {
setVeterinaryInfoForm(toVeterinaryInfoForm(selectedBird));
} else {
setVeterinaryInfoForm(emptyVeterinaryInfoForm);
}
setEditingVeterinaryInfo(false);
}, [selectedBird]);
const overviewWindowStartDate = useMemo(() => {
const startDate = new Date();
startDate.setHours(0, 0, 0, 0);
@@ -2444,6 +2488,7 @@ function App() {
setVetVisits([]);
setMedications([]);
setMedicationAdministrations([]);
setVeterinaryInfoForm(emptyVeterinaryInfoForm);
return;
}
@@ -3504,6 +3549,49 @@ function App() {
}
};
const handleVeterinaryInfoSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!selectedBird || savingVeterinaryInfo) {
return;
}
setError('');
setSavingVeterinaryInfo(true);
try {
const response = await apiFetch(`/birds/${selectedBird.id}`, authToken, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...toBirdForm(selectedBird),
...veterinaryInfoForm,
}),
});
if (!response.ok) {
throw new Error(await readErrorMessage(response, 'Unable to save veterinary info.'));
}
const data = await readJsonSafely<{ bird?: Bird }>(response);
if (!data?.bird) {
throw new Error('Unable to save veterinary info.');
}
const savedBird = data.bird;
setBirds((current) => sortBirdsByName(current.map((bird) => (bird.id === savedBird.id ? savedBird : bird))));
setVeterinaryInfoForm(toVeterinaryInfoForm(savedBird));
setEditingVeterinaryInfo(false);
if (editingBirdId === savedBird.id) {
setBirdForm(toBirdForm(savedBird));
}
} catch (submitError) {
setError(submitError instanceof Error ? submitError.message : 'Unable to save veterinary info.');
} finally {
setSavingVeterinaryInfo(false);
}
};
const handleWeightSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -5673,6 +5761,43 @@ function App() {
placeholder="Optional"
/>
</label>
<div className="settings-inline-header wide-field">
<p className="eyebrow">Veterinary</p>
<h3>Clinic account</h3>
</div>
<label>
Clinic name
<input
value={birdForm.vetClinicName}
onChange={(event) => setBirdForm({ ...birdForm, vetClinicName: event.target.value })}
placeholder="Optional"
/>
</label>
<label>
Account #
<input
value={birdForm.vetAccountNumber}
onChange={(event) => setBirdForm({ ...birdForm, vetAccountNumber: event.target.value })}
placeholder="Optional"
/>
</label>
<label>
Dr. name
<input
value={birdForm.vetDoctorName}
onChange={(event) => setBirdForm({ ...birdForm, vetDoctorName: event.target.value })}
placeholder="Optional"
/>
</label>
<label className="wide-field">
Clinic address
<textarea
rows={2}
value={birdForm.vetClinicAddress}
onChange={(event) => setBirdForm({ ...birdForm, vetClinicAddress: event.target.value })}
placeholder="Optional"
/>
</label>
<label className="wide-field">
Motivators
<div className="profile-list-fields">
@@ -6387,6 +6512,105 @@ function App() {
{selectedBirdTab === 'vet' ? (
<div className="flock-member-sections" role="tabpanel">
<section className="panel inset-panel">
<div className="panel-header">
<div>
<p className="eyebrow">Veterinary</p>
<h2>Clinic account</h2>
</div>
{!editingVeterinaryInfo ? (
<button
className="profile-icon-button"
onClick={() => {
setVeterinaryInfoForm(toVeterinaryInfoForm(selectedBird));
setEditingVeterinaryInfo(true);
}}
type="button"
aria-label={`Edit veterinary info for ${selectedBird.name}`}
title="Edit veterinary info"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M4 20h4l10.5-10.5-4-4L4 16v4Z" />
<path d="m13.5 6.5 4 4" />
<path d="M15 5l1.5-1.5a2.1 2.1 0 0 1 3 3L18 8" />
</svg>
</button>
) : null}
</div>
{editingVeterinaryInfo ? (
<form className="form-panel inline-form care-entry-form" onSubmit={handleVeterinaryInfoSubmit}>
<label>
Clinic name
<input
value={veterinaryInfoForm.vetClinicName}
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetClinicName: event.target.value })}
placeholder="Optional"
/>
</label>
<label>
Account #
<input
value={veterinaryInfoForm.vetAccountNumber}
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetAccountNumber: event.target.value })}
placeholder="Optional"
/>
</label>
<label>
Dr. name
<input
value={veterinaryInfoForm.vetDoctorName}
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetDoctorName: event.target.value })}
placeholder="Optional"
/>
</label>
<label className="wide-field">
Clinic address
<textarea
rows={2}
value={veterinaryInfoForm.vetClinicAddress}
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetClinicAddress: event.target.value })}
placeholder="Optional"
/>
</label>
<div className="button-row care-form-actions">
<button className="primary-button" type="submit" disabled={savingVeterinaryInfo}>
{savingVeterinaryInfo ? 'Saving veterinary info...' : 'Save veterinary info'}
</button>
<button
className="secondary-button"
onClick={() => {
setVeterinaryInfoForm(toVeterinaryInfoForm(selectedBird));
setEditingVeterinaryInfo(false);
}}
type="button"
disabled={savingVeterinaryInfo}
>
Cancel
</button>
</div>
</form>
) : (
<div className="detail-grid">
<article className="detail-card">
<span>Clinic name</span>
<strong>{selectedBird.vetClinicName || 'Not recorded'}</strong>
</article>
<article className="detail-card">
<span>Account #</span>
<strong>{selectedBird.vetAccountNumber || 'Not recorded'}</strong>
</article>
<article className="detail-card">
<span>Dr. name</span>
<strong>{selectedBird.vetDoctorName || 'Not recorded'}</strong>
</article>
<article className="detail-card wide-field">
<span>Clinic address</span>
<strong>{selectedBird.vetClinicAddress || 'Not recorded'}</strong>
</article>
</div>
)}
</section>
<section className="panel inset-panel">
<div className="panel-header">
<div>
<p className="eyebrow">Vet visits</p>
@@ -7459,6 +7683,43 @@ function App() {
placeholder="Optional"
/>
</label>
<div className="settings-inline-header wide-field">
<p className="eyebrow">Veterinary</p>
<h3>Clinic account</h3>
</div>
<label>
Clinic name
<input
value={birdForm.vetClinicName}
onChange={(event) => setBirdForm({ ...birdForm, vetClinicName: event.target.value })}
placeholder="Optional"
/>
</label>
<label>
Account #
<input
value={birdForm.vetAccountNumber}
onChange={(event) => setBirdForm({ ...birdForm, vetAccountNumber: event.target.value })}
placeholder="Optional"
/>
</label>
<label>
Dr. name
<input
value={birdForm.vetDoctorName}
onChange={(event) => setBirdForm({ ...birdForm, vetDoctorName: event.target.value })}
placeholder="Optional"
/>
</label>
<label className="wide-field">
Clinic address
<textarea
rows={2}
value={birdForm.vetClinicAddress}
onChange={(event) => setBirdForm({ ...birdForm, vetClinicAddress: event.target.value })}
placeholder="Optional"
/>
</label>
<label className="wide-field">
Motivators
<div className="profile-list-fields">