Stu Mason
Stu Mason

Activity

StuMason/cleanconnect
TidyLinker.com
TypeScript
Pull Request Merged

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_enabled flags, 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_SECRET prefix — 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_due flag — warns when scheduled time has passed but status is still open / pending / in_progress. Job 55 sat in in_progress for 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). StripeService is 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 --dirty clean
  • npm run format clean, eslint clean on touched file
  • tsc --noEmit no new errors in Show.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
+845
additions
-111
deletions
3
files changed