Repository navigation
feat(patient): care hub, visit queue, access log and payment receipts - #138
Open
classikdev wants to merge 6 commits into
Open
classikdev wants to merge 6 commits into
classikdev wants to merge 6 commits into
Conversation
Ranks the dependents and health-profile data already available into Action needed / Coming up / Recent, fetches every source in parallel, and degrades one failing source to its own notice instead of blanking the hub. A source that cannot be queried yet (no care profile selected) reports unavailable rather than loading, so the screen no longer spins forever, and is never read as an empty profile. Sections for unbuilt features stay hidden; Coming up waits on DocForum#3. Closes DocForum#33
…ocForum#33 Captured at 1440 and 390 CSS px against a local stub of docforum-core seeded with fixture dependents and health entries. The third image is the state reachable in a real session today, with no care-profile id to query health with.
|
@classikdev 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! 🚀 |
…payment history with receipts Three patient follow-ons to the care hub, each built against a documented assumed core contract so the UI lands ahead of the backend. A route that is not deployed yet reads as "not available for this deployment yet", never as an empty list or a zero. DocForum#32: GET /appointments/mine (real) feeds the hub's Coming up section and the new /patient/appointments/:appointmentId detail page, which polls the assumed queue route every 15s and posts check-in. The id is matched against the patient's own appointments before the queue route is ever called. DocForum#34: /account/access-log lists who opened what, when and why, with from/to/recordType filters and cursor paging, against the assumed GET /patients/me/access-log. DocForum#37: /payments and /payments/:intentId read the assumed patient-scoped GET /payments/mine routes (the existing intents routes are admin-only), format stroops as XLM, word each escrow status, link the testnet explorer, and render a printable receipt. All three names are me-scoped because middleware/auth.ts exposes only {id, role}, so the frontend cannot know its own patientProfileId. Closes DocForum#32 Closes DocForum#34 Closes DocForum#37
…sktop and mobile widths Captured at 1440 and 390 CSS px against a local stub of docforum-core seeded with fixture appointments, queue positions, access-log entries and payment intents, so the states behind core#129, core#45 and core#90 are reviewable before those backends land. The receipt shot is the reason the receipt's two-column grid collapses below 760px: at 390px the value column was starved and the transaction hash was unreadable.
Contributor
|
@classikdev fix conflicts |
classikdev
marked this pull request as ready for review
October 17, 2026 05:40
Author
|
done @afeezorobsco-cyber |
Contributor
|
This PR has merge conflicts with |
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.
Closes #33
Closes #32
Closes #34
Closes #37
Four patient-side issues in one PR. #33 is the hub; #32, #34 and #37 are the three screens it now links to.
What changed
#33 — patient home as a care hub
PatientDashboardPagewas a placeholder with two links. It is now an action-first hub that answers what do I need to do?src/features/care-hub/ranking.ts— the ranking model, pure and UI-free: aCareCardis{id, section, priority, title, detail, to, badge?}, sections areaction | upcoming | recent, and priorities are a named scale (blockedNow 10 → needsYou 20 → goodToKnow 30 → recent 40).rankCardssorts by priority then title and does not mutate its input;groupBySectionsplits the ranked list. Keeping this a plain module is what makes the ordering testable without a DOM.src/features/care-hub/hooks/useCareHub.ts— fans out to the sources in parallel:useDependents(),useMyAppointments(), plus oneuseHealthEntries(section)per section. Independent queries, no waterfall, each with its own status. It derives cards from what came back: a visit today (needsYou, badgedToday— it leads Action needed because check-in is open right now), a later visit (goodToKnow, underComing up), an expired handover invite (blockedNow), a dependent eligible for handover (needsYou), a health profile that is genuinely empty (goodToKnow), and health entries updated in the last 30 days underRecent.src/pages/PatientDashboardPage.tsx— renders the three sections, eacharia-labelledbyits own heading, cards linking to the detail flow they belong to, and nav links to the two new screens (/payments,/account/access-log).Coming upandRecentrender only when they have cards, so an unbuilt feature is absent rather than an empty shell.#32 — visit check-in with a live queue position
src/services/appointments-service.ts—listMyAppointments()reads the realGET /appointments/mine(a bare array, newest-first, withavailabilitySlotincluded). The queue routes are assumed (below).src/features/appointments/slot.ts— pure date logic. "On the day" is the patient's local calendar day, not a UTC midnight, because the slot instants come from Postgres.isUpcomingkeeps a visit inComing upwhile it is ahead of you or happening today and drops older ones without a server-side filter;shouldPollQueueonly ever polls ascheduledvisit that is today.src/features/appointments/queue.ts— the alert rules:NOTIFY_AT_OR_BELOW = 2per the issue's "1–2 patients ahead",being_seenalways alerts, andqueueSignature(phase:peopleAhead) makes two identical reads produce one alert while 3 → 2 → 1 produces three.canCheckIntreats the window as the backend's call:waiting/being_seenis always open, anot_openwhosecheckInOpensAthas already passed is offered (the projection just has not recomputed), and a missing window is not treated as a closed one.src/features/appointments/hooks/useAppointmentQueue.ts—QUEUE_POLL_INTERVAL_MS = 15_000withgcTime: 0, so a returning patient never sees a stale position;useCheckInPOSTs and invalidates;useQueueNotificationfires once per distinct state.src/hooks/useBrowserNotification.ts— permission is requested from the click on "Notify me", never at page load, asked for once per session, and stays silent when permission isdeniedorNotificationis undefined (Firefox on Android, and any insecure origin).src/pages/AppointmentDetailPage.tsxat/patient/appointments/:appointmentId— the id is matched against the patient's own/appointments/minelist before the queue route is ever called, so a forwarded or guessed id cannot probe someone else's visit, and the page shows a not-found state instead. The panel itself renders position, people ahead, the wait estimate labelled as an estimate, and the checked-in timestamp.#34 — who viewed my records
src/services/access-log-service.ts—GET /patients/me/access-log?from&to&recordType&cursor(assumed, core#45).src/features/account/copy.ts— the wording layer for a privacy screen:roleLabel(doctor → Clinician,patient → You,admin → DocForum staff),recordLabel,reasonCopy, each with a fallback string so a reason core adds before this repo catches up renders as "Opened as part of your care." rather thanundefined.formatWhenshows the raw value if the timestamp does not parse.reportConcernHref()returnsnullunlessVITE_SUPPORT_EMAILis set — the page then says plainly that no support channel is configured for this deployment, because pointing a worried patient at an address that does not exist is worse than admitting the gap.src/pages/AccessLogPage.tsx+AccessLogFilters/AccessLogList/AccessLogError— from/to/type filters with an explicit Apply (typing a date must not fire four requests),useInfiniteQuerywith a "Show older entries" button, and a per-row who · what · when · why.#37 — payment history and receipts
src/services/payment-history-service.ts—GET /payments/mineandGET /payments/mine/:intentId(assumed, core#90). The real/payments/intents...routes are admin-only, which is why these aremine-scoped rather than a filter on the existing list.src/features/payments/stroops.ts— stroops → XLM entirely inBigIntand string manipulation.amountStroopsarrives as a string because the column is a BigInt; routing it through a JSnumberwould silently round anything over 2^53. Values that are not a plain non-negative integer string returnnulland the caller shows"<raw> stroops"— an amount this helper cannot parse is still visible, never replaced by a plausible-looking number.src/features/payments/status.ts— a sentence per escrow state in the payer's terms ("DocForum is holding this money in escrow. The facility has not been paid yet — that is the protection escrow exists for."),refundDeadlineCopy(absent deadline renders nothing;refundDeadlineAtis an assumed column), andhistoryErrorCopy, which distinguishes a 404 ("not available for this deployment yet") from a failed request from an actually-empty history.src/features/payments/explorer.ts— one place for the testnet explorer base (VITE_STELLAR_EXPLORER_BASE_URL), used for both the transaction and the escrow contract links.PaymentHistoryList/PaymentReceipt/PaymentHistoryPage/PaymentDetailPage— a cursor-paged list at/payments, and a detail page whose receipt prints cleanly (@media printdrops the app chrome;PaymentReceiptrestates itself in black on white).Design decisions and trade-offs
isPendingforever, so the first cut of the hub spun on "Loading your care summary…" indefinitely and never showed the cards it did have. Found by rendering it, not by reading it.SourceStatusnow carriesisLoading | failed | unavailable, and only the first two are ever reported to the patient.me-scoped.middleware/auth.tsexposes only{id, role}, so the frontend cannot know its ownpatientProfileId. Every route added here is named from the session (/patients/me/access-log,/payments/mine) rather than taking an id the client would have to guess.CareCardis presentation-only. It never crosses the wire in either direction, so hard rule 2 (mirrordocforum-coreshapes, no parallel frontend entities) holds; every field on it comes from a realDependent,AppointmentorHealthEntry. Same for the derivedAccessLogEventandPaymentHistoryEntrytypes — they mirror the assumed core rows, they do not invent a frontend model.scheduledappointment on the patient's local today. A cancelled or past visit cannot keep a background poll alive.Userhas no display name, so interpolating the address into the H1 read badly and overflowed at 390px./account/access-logand [Phase W2] Payment history and receipts for patients #37 names/payments, so those stay at the literal paths, guarded topatient, instead of being folded under/patientwhere the hub's nested routes live. [Phase W2] Check-in and live queue position on appointment day #32's detail page has no path in the issue, so it nests.Known assumptions
Full text in
ARCHITECTURE_ESSENTIALS.md→ "Known assumptions — Phase W2 patient follow-ons". Summary:GET /appointments/:appointmentId/queue,POST /appointments/:appointmentId/check-in,AppointmentQueue/appointments/mine; the queue panel reports "not available for this deployment yet"GET /patients/me/access-log?from&to&recordType&cursor,AccessLogEventGET /payments/mine,GET /payments/mine/:intentId, plusfacilityName/refundDeadlineAton the row{items, nextCursor}getNextPageParamtreats anullcursor as the endOne gap worth a core issue on its own:
GET /appointments/mineincludesavailabilitySlotbut no clinician or facility name, so the hub and the appointment page can say when a visit is but not with whom. The UI does not invent a name for it.Screenshots
Captured at 1440 and 390 CSS px. Method, so nobody misreads these: the app was built against a local stub of
docforum-coreserving fixture appointments, queue states, access-log entries and payment intents, then rendered in iframes pinned to those exact widths. Not the Render preview backend, and the fixture names are not real accounts. The states behind core#129/#45/#90 are only reviewable this way — that is the point of committing the images.The mobile receipt is the one image that changed code: at 390px the two-column label/value grid starved the value column and the 64-character transaction hash was unreadable, so
PaymentReceipt.module.csscollapses to one column below 760px. The #33 screenshots from the earlier commits are still indocs/screenshots/2026-10-16-patient-care-hub/, including the no-care-profile state that only a real session reaches today —useProfileStoreis never populated by production code, so that remains adocforum-coreAPI gap, not something to paper over withprofileId === userId.Commands run, with output
Node 24.21.0 locally; CI pins Node 20. The only CI gate I cannot reproduce is the runner itself.
Baseline, measured not remembered:
origin/mainis 11 files / 44 tests, all green (checked in a throwaway worktree of the untouched commit,node_modulessymlinked). This PR is 25 files / 156 tests, so +14 files and +112 tests are attributable to this diff:ranking.test.ts(5),PatientDashboardPage.test.tsx(13, +5 for appointment-sourced cards)queue.test.ts(13),slot.test.ts(6),useAppointmentQueue.test.tsx(4),QueuePanel.test.tsx(11),AppointmentDetailPage.test.tsx(6),useBrowserNotification.test.ts(7)AccessLogPage.test.tsx(9)stroops.test.ts(6),status.test.ts(11),explorer.test.ts(2),PaymentHistoryPage.test.tsx(8),PaymentDetailPage.test.tsx(11)Cases a reviewer should check rather than trust:
queue.test.tsasserts two identical reads produce one notification and 3 → 2 → 1 produces three;AppointmentDetailPage.test.tsxasserts the queue route is never called for an id that is not the patient's own;PaymentHistoryPage.test.tsxandAccessLogPage.test.tsxeach assert a 404 renders "not available for this deployment yet" and not an empty state;PaymentDetailPage.test.tsxasserts an unauthenticated visit never calls the service at all;PatientDashboardPage.test.tsxasserts a failed appointments request still renders the dependent and health cards.Four
act(...)warnings surfaced while writing these tests — permission resolution inuseBrowserNotification.test.ts, the poll timer inuseAppointmentQueue.test.tsx— are fixed by wrapping the awaited state update inact. The one warning that remains, insrc/components/RequireAuth.test.tsx, predates this PR and is on a file this diff does not touch, so it is left alone rather than quietly improved.Which ROADMAP.md item does this close/progress?
Phase W2. The care-hub line (#33) was checked off in
1e1d093; #32, #34 and #37 are now checked off with their issue links, each naming the unlanded core PR it is waiting on. The W2 header line names all of them. Changelog entries were added per issue, and the earlier entry recording the "stays unimplemented" verdict is annotated as corrected by the entries that follow it.Deliberately left out
docforum-corecontract (its [Phase W2] Orders list + facility-selection screen #5–[Phase W2] Waitlist: join, manage, and accept time-boxed slot offers #16), so per hard rule 1 they are not invented here. Each is one more source inuseCareHub, which is the point of the fan-out shape./appointments/mineresponse does not carry one. Inventing a label would be a claim about someone's care provider.PaymentIntentPanel(create/fund) is untouched and still the only write path.PRD flow
Implements the patient-side entry point of PRD §4 in
docforum-core— the step that sequences book → attend → follow-up for one patient — plus the two accountability surfaces the PRD's escrow and privacy sections promise the patient: a receipt for money that left them, and a log of who read their record. No screen outside the patient's own flows changed.Docs updated in this PR