PR #169 merged: feat: admin job page operational details (contact, payment, Stripe Connect)
Why
While digging into job #6AgxZben — a live no-show/wrong-door situation between a client and a cleaner — it became obvious the admin job page is missing the data we actually need to triage these calls in the moment.
Specifically: no phone numbers on either party, no payment state, no Stripe Connect status, no signal that the scheduled date has already passed. The Stripe dashboard PI lives only in the DB, so we'd been jumping between Stripe and the admin UI to piece any of this together.
Summary
Read-only surfacing of the data needed to support an in-flight booking from a single screen:
- Party cards (client + cleaner) — phone (
tel:), email (mailto:), email-verified state, last login. Client with no phone gets an explicit amber warning and a tip about why we ask for it. Cleaner card also shows Stripe Connect status,charges_enabled/payouts_enabledflags, and an "Open in Stripe" deep-link. - Payment card — status badge, amount breakdown (cleaner / platform / total), authorize/capture/fail timestamps, failure reason, payout state, and a direct link to the Stripe dashboard Payment Intent. The dashboard URL switches between live and test based on the configured
STRIPE_SECRETprefix — no separate env var needed. - "Other jobs between these two" mini-list — quick relationship context, useful when deciding whether to escalate or whether this looks like a one-off.
is_past_dueflag — warns when scheduled time has passed but status is stillopen/pending/in_progress. Job 55 sat inin_progressfor over a week because the parties were rescheduling in chat; nothing on the page hinted at that.
Where the data was already eager-loaded (e.g. quotes), I extended the existing query rather than adding a new one. Two new queries: quotes.payment.payout (a single extra hop) and the related-jobs lookup (single where(client_id)->where(cleaner_id), limit 10).
What's deliberately NOT in this PR
- Admin actions on payments (capture / refund / release authorization).
StripeServiceis currently a 6-line wrapper — no capture/refund code exists yet. Money-moving needs a service layer, an audit log, a confirm dialog with a typed reason, and a written policy on what to do for no-shows. A "Capture £80" button I shipped today could be the wrong call on a different job tomorrow. Sat down with Stu, agreed to spec the policy first. - Notification-delivery audit. Kateryna's "I don't get notifications" claim (which surfaced this whole investigation) needs its own table + capture pipeline. Separate feature.
- Dispute / incident notes thread. Separate table, separate UI.
Test plan
-
php artisan test tests/Feature/Admin/JobControllerTest.php— 32 passing -
vendor/bin/pint --dirtyclean -
npm run formatclean, eslint clean on touched file -
tsc --noEmitno new errors inShow.tsx - Manual: visit
/admin/jobs/<uid>on a job with a payment + Connect account + past-due date and confirm each section renders crisp - Manual: visit on a fresh job with no quotes/payment and confirm cards collapse gracefully
- Manual: click the Stripe dashboard link from prod with live keys and confirm it lands on the right PI without
/test