Repository navigation
Conversation
… clock
`mockNotificationStream.test.ts` asserted that a freshly emitted mock
notification is the *first* entry of `GET /api/notifications`. The seeded
fixtures are stamped with fixed hours of the day (10:00 / 11:00 local), so
whenever the suite runs before 11:00 the fixture sorts ahead of the
emitted notification and the test fails.
Look the notification up by id instead of by position — the test's intent
("stores it for GET /api/notifications") is preserved and the failure goes
away.
This is unrelated to the custody work that follows; it is included so the
whole suite is green on this branch.
Epic D — Temporary Custody · Phase 3 backlog item amina69#33 Refs amina69#628 Adds the query and mutation hooks for the Temporary Custody domain. ### Hooks (`src/features/custody/hooks/`) - `useCustodyList(params)` — the list, keyed by `["custody", params]`, one cache entry per filter/page combination. - `useCustodyDetail(custodyId)` — a single arrangement, keyed by `["custody-detail", custodyId]`, with the escrow snapshot read **alongside** it from the existing escrow endpoint (`GET /escrow/:id/status`). The escrow state is always server-sourced; it is never derived client-side from the arrangement's own `status`/`escrowStatus`. - `useRequestCustody()` — creates an arrangement and navigates to it. - `useExtendCustody(custodyId)` / `useEndCustody(custodyId)` — extend or end an arrangement. ### Query keys The list and the detail deliberately live under **separate roots** (`["custody"]` vs `["custody-detail", id]`) so every mutation can invalidate *exactly* the keys it affects: | Mutation | Invalidated | Untouched | | --- | --- | --- | | `useRequestCustody` | every list variant, the created arrangement's detail | other details, escrow caches, other domains | | `useExtendCustody` | every list variant, that arrangement's detail | other details, escrow caches, other domains | | `useEndCustody` | every list variant, that arrangement's detail | other details, escrow caches, other domains | The scope is asserted in `src/features/custody/hooks/__tests__/useCustodyMutations.test.tsx` by seeding the cache with the affected *and* the unaffected keys and checking which ones end up invalidated. ### Supporting changes - `escrowService.getStatus()` — exposes the existing escrow status endpoint to the feature layer. - `CustodyArrangement.escrowId` — the arrangement can now point at the escrow backing it (the arrangement id is used as a fallback). - The MSW custody handlers now serve the whole Temporary Custody API (list / detail / request / extend / end) from a small in-memory store, and `CustodyStatusBadge` learned the lower-case Temporary Custody statuses.
Epic D — Temporary Custody · Phase 3 backlog item amina69#35 Refs amina69#630 Adds `src/components/forms/DateRangePicker.tsx` plus the calendar-date helpers it is built on. ### `src/lib/dateOnly.ts` Everything works on **date-only strings** (`"YYYY-MM-DD"`). A custody period is a range of calendar days, not a range of instants, so the helpers avoid `new Date(str)` for date-only input — that parses as *UTC* midnight and is the previous day for anybody west of Greenwich. Day arithmetic runs on UTC calendar-day ordinals, so a daylight-saving switch can never shorten or lengthen a day. ### `DateRangePicker` - Keyboard accessible: arrow keys move the focus by a day/week, `Home`/`End` jump to the start/end of the week, `PageUp`/`PageDown` change the month and `Enter`/`Space` pick a day. The grid follows the `grid` → `row` → `gridcell` structure and announces the selected days with `aria-pressed`. - Days before `minDate` (today by default) and days the API reported as unavailable are rendered as disabled. - A range that would cross an unavailable day is **blocked** and explained ("The selected period includes 12 Jun 2026, which is not available. Pick another end date."). - The number of nights in the selected range is shown. ### Tests `src/lib/__tests__/dateOnly.test.ts` re-runs the day arithmetic in six time zones (UTC, Europe/Berlin, America/New_York, Asia/Tokyo, UTC+14 and UTC-11) and asserts the results never change — including across both the US and the EU daylight-saving edges. `src/components/forms/__tests__/DateRangePicker.test.tsx` covers the disabled days, the blocked range, the nights count and the keyboard interaction.
Epic D — Temporary Custody · Phase 3 backlog item amina69#34 Refs amina69#629 Adds the two-step custody request flow on `/pets/:petId/custody/new`. ### `CustodyRequestForm` - **Pet summary header** — the pet the request is for. - **Custody period** — the `DateRangePicker` from amina69#630, with the days the API reported as unavailable disabled and a range that crosses one of them blocked. - **Caretaker** — a select of the people who can look after the pet, plus a "somebody else" option that asks for a name. - **Emergency contact** and **notes** — both optional. - **Confirmation step** — summarises the period, the caretaker and the emergency contact, and explains what happens next (the caretaker is notified, an escrow is created, the period starts on the first day). The payload is validated with `custodyRequestSchema` **before** the request is sent, so an invalid form never reaches the network. Field-level errors are rendered inline next to the field they belong to, and the submit button is disabled while the request is pending. ### Supporting changes - `GET /custody/availability` — the days a pet is already booked for, plus the `useCustodyAvailability` hook that reads them. The query lives under the custody list root, so it is refreshed by the same invalidation. - `petService.getPet()` and `GET /api/pets/:id` — the pet summary header needs the pet. - `emergencyContact` is now part of the custody request payload. - The sign-in form mirrors the signed-in user into `localStorage` so the app can tell which side of an arrangement the current user is on.
Epic D — Temporary Custody · Phase 3 backlog item amina69#36 Refs amina69#631 Adds the custody dashboard on `/custody`. ### Tabs and URL Three tabs — **Active**, **Upcoming** and **Past** — each mapping to the statuses the API can be asked for (`active`/`disputed`, `requested`/`approved`, `completed`/`cancelled`). The list endpoint accepts a comma-separated status filter, so a tab is always filtered by the server. Both the active tab and the page live in the URL (`?tab=upcoming&page=2`), so a view can be reloaded or shared. Switching tab resets the page. ### `CustodyCard` Shows the pet, the counterpart, the dates, the status badge and the number of days left (or days until the period starts, or how long ago it ended). The **perspective** depends on the signed-in user: an owner sees the card written from the owner's side ("Caretaker: Jane Doe", "You are the owner"), a caretaker sees it from the caretaker's side ("Owner: Sam Smith"). A viewer who is neither party gets a neutral card without a perspective label. The signed-in user is read from the session the sign-in form mirrors into `localStorage` (`useCustodyViewer`). That hook is a deliberately small, custody-scoped stand-in: when the shared `useCurrentUser` hook (issue amina69#650) lands it should delegate to it, and nothing else has to change. ### Supporting changes - `CustodyArrangement` may now carry the display names (`petName`, `ownerName`, `caretakerName`); the card falls back to the ids when the API does not send them.
Describes the routes, the query keys (and the invalidation scope of every mutation), the endpoints, the date-only conventions and how the signed-in user is resolved.
|
@Mikolo37 is attempting to deploy a commit to the sheriffamina1-9497's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Mikolo37 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Owner
|
@Mikolo37 kindly fix the conflict |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Epic D — Temporary Custody · Phase 3 backlog items #33, #34, #35 and #36.
Closes #628
Closes #629
Closes #630
Closes #631
Summary
This PR delivers the Temporary Custody flow end to end: the query and
mutation hooks, a keyboard-accessible date range picker, the two-step
custody request form and the dashboard.
The work is split over four focused commits, one per backlog item. The
commit order is hooks → picker → form → dashboard because the request
form builds on the picker; the items themselves are listed below in backlog
order.
feat(custody)useCustodyList,useCustodyDetail,useRequestCustody,useExtendCustody,useEndCustodyfeat(forms)src/components/forms/DateRangePicker.tsx+src/lib/dateOnly.tsfeat(custody)src/features/custody/components/CustodyRequestForm.tsxon/pets/:petId/custody/newfeat(custody)src/pages/CustodyDashboardPage.tsx+src/features/custody/components/CustodyCard.tsx#628 — Custody query and mutation hooks
src/features/custody/hooks/useCustodyList(params)["custody", params]useCustodyDetail(custodyId)["custody-detail", custodyId]useRequestCustody()useExtendCustody(custodyId)useEndCustody(custodyId)Invalidation scope (the acceptance criterion)
The list and the detail deliberately live under separate roots
(
["custody"]vs["custody-detail", id]), so a mutation can invalidateexactly the keys it affects:
useRequestCustodyuseExtendCustodyuseEndCustodyThis is asserted in
src/features/custody/hooks/__tests__/useCustodyMutations.test.tsx: thecache is seeded with the affected and the unaffected keys (a second list
variant, another arrangement's detail, an escrow cache, an adoption cache
and a disputes cache) and the test checks which of them end up invalidated.
Escrow data
useCustodyDetailreads the escrow from the existing escrow endpoint(
GET /escrow/:id/status, now exposed asescrowService.getStatus) insidethe same cache entry as the arrangement, so invalidating the detail key
refreshes both. The escrow state is therefore always server-sourced — it is
never derived client-side from the arrangement's own
status/escrowStatus.A test proves it: the arrangement claims
escrowStatus: "FUNDED"while theescrow endpoint reports
RELEASED, and the hook surfacesRELEASED.CustodyArrangementgained an optionalescrowId; the arrangement id isused as a fallback when the API does not expose one (the same convention the
settlement summary page already uses for adoptions).
#630 — Date range picker with availability conflicts
src/components/forms/DateRangePicker.tsx+src/lib/dateOnly.tsHome/Endjump to the start/end of the week,
PageUp/PageDownchange the month andEnter/Spacepick a day. The grid follows thegrid→row→gridcellstructure and announces the selected days witharia-pressed.minDate(today by default) and the daysthe API reported as unavailable are rendered as disabled.
with an explanatory message ("The selected period includes 12 Jun 2026,
which is not available. Pick another end date.").
No off-by-one around DST and UTC-offset boundaries
A custody period is a range of calendar days, not a range of instants, so
every date is a date-only string (
"YYYY-MM-DD").src/lib/dateOnly.tsnever uses
new Date("2026-06-20")— that is parsed as UTC midnight and isthe previous day west of Greenwich — and the day arithmetic runs on UTC
calendar-day ordinals, so a daylight-saving switch can neither shorten nor
lengthen a day.
src/lib/__tests__/dateOnly.test.tsre-runs the arithmetic in six time zones(UTC, Europe/Berlin, America/New_York, Asia/Tokyo, UTC+14 and UTC-11) and
asserts the results never change — including across both the US and the EU
daylight-saving edges (8 March and 1 November 2026, 29 March and 25 October
2026).
#629 — Custody request form
src/features/custody/components/CustodyRequestForm.tsxon/pets/:petId/custody/newas unavailable disabled and a range crossing one of them blocked.
"somebody else" option that asks for a name.
emergency contact, and explains what happens next (the caretaker is
notified, an escrow is created, the period starts on the first day).
The payload is validated with
custodyRequestSchemabefore the requestis sent, so an invalid form never reaches the network (asserted in
CustodyRequestForm.test.tsx— thePOST /custodyhandler is never called).Field-level errors are rendered inline next to the field they belong to, and
the submit button is disabled while the request is pending.
Supporting changes
GET /custody/availability— the days a pet is already booked for, plusthe
useCustodyAvailabilityhook that reads them. The query lives underthe custody list root, so it is refreshed by the same invalidation.
petService.getPet()andGET /api/pets/:id— the pet summary headerneeds the pet.
emergencyContactis now part of the custody request payload.#631 — Custody dashboard
src/pages/CustodyDashboardPage.tsxon/custody+src/features/custody/components/CustodyCard.tsxcan be asked for (
active/disputed,requested/approved,completed/cancelled); the list endpoint now accepts a comma-separatedstatus filter, so a tab is always filtered by the server.
(
/custody?tab=upcoming&page=2), so a view can be reloaded or shared.Switching tab resets the page.
number of days left (or the days until the period starts, or how long ago
it ended).
("Caretaker: Jane Doe", "You are the owner"), a caretaker sees it from the
caretaker's side ("Owner: Sam Smith"). A viewer who is neither party gets
a neutral card without a perspective label.
About
useCurrentUserThe issue refers to
useCurrentUser(backlog item #55, issue #650), whichis still open and owned by another contributor. Rather than duplicate that
work, this PR adds a small, custody-scoped stand-in —
useCustodyViewer(src/features/custody/hooks/useCustodyViewer.ts) — thatreads the signed-in user from the session the sign-in form mirrors into
localStorage(the same convention the app already uses forpetad_user_role). When #650 lands,useCustodyViewershould delegate toit; nothing else has to change.
Also in this PR
fix(notifications)—mockNotificationStream.test.tsasserted that afreshly emitted mock notification is the first entry of
GET /api/notifications. The seeded fixtures are stamped with fixed hoursof the day (10:00 / 11:00 local), so the test failed whenever the suite ran
before 11:00. It now looks the notification up by id. Unrelated to the
custody work — included so the whole suite is green.
docs/custody.md— the routes, the query keys, the invalidation scope,the endpoints and the date-only conventions.
Testing
New tests:
src/features/custody/hooks/__tests__/useCustodyList.test.tsxsrc/features/custody/hooks/__tests__/useCustodyDetail.test.tsxsrc/features/custody/hooks/__tests__/useCustodyMutations.test.tsxsrc/lib/__tests__/dateOnly.test.tssrc/components/forms/__tests__/DateRangePicker.test.tsxsrc/features/custody/components/__tests__/CustodyRequestForm.test.tsxsrc/features/custody/components/__tests__/CustodyCard.test.tsxsrc/pages/__tests__/CustodyRequestPage.test.tsxsrc/pages/__tests__/CustodyDashboardPage.test.tsxHow to review
The branch is a stack — each commit builds on the previous one:
fix(notifications)— the unrelated test fix.feat(custody): add custody list/detail query hooks and scoped mutations([033] Add custody query and mutation hooks #628)feat(forms): add a keyboard-accessible date range picker([035] Date-range picker with availability conflicts #630)feat(custody): add the temporary custody request form([034] BuildCustodyRequestForm#629)feat(custody): add the temporary custody dashboard([036] BuildCustodyDashboardPage#631)docs(custody): document the temporary custody featureThe same four pieces of work are also pushed as standalone branches
(
feat/custody-hooks,feat/date-range-picker,feat/custody-request-formand
feat/custody-dashboard) in case they are easier to review one by one.