-
Notifications
You must be signed in to change notification settings - Fork 1
feat(#1466): non-blocking insurance prompt read-model + urgency badge (P3a) #1471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| 'use client' | ||
|
|
||
| /** | ||
| * Insurance-prompt card — Automation Gate P3a (#1466). | ||
| * | ||
| * A PERSISTENT, NON-BLOCKING prompt rendered above the grouped automation jobs | ||
| * while any traveler's insurance decision is still pending. It deep-links to the | ||
| * Insurance tab (?tab=insurance) — it does NOT re-implement the proposal dialog. | ||
| * | ||
| * The urgency badge is DISPLAY-ONLY (Al: purchase_window_hours never gates). | ||
| * The underlying read-model fails open to "no badge" / no card on any error, so | ||
| * this can never block the Automation tab from rendering. | ||
| */ | ||
|
|
||
| import * as React from 'react' | ||
| import { useRouter } from 'next/navigation' | ||
| import { Shield, ChevronRight } from 'lucide-react' | ||
| import { Button } from '@/components/ui/button' | ||
| import { useInsurancePrompt } from '@/hooks/use-automations' | ||
| import type { InsuranceUrgencyTier } from '@tailfire/shared-types/api' | ||
|
|
||
| interface InsurancePromptCardProps { | ||
| tripId: string | ||
| } | ||
|
|
||
| const URGENCY_BADGE: Record< | ||
| Exclude<InsuranceUrgencyTier, 'none'>, | ||
| { label: string; className: string } | ||
| > = { | ||
| urgent: { label: 'Purchase window closing', className: 'bg-red-100 text-red-700' }, | ||
| soon: { label: 'Purchase window soon', className: 'bg-amber-100 text-amber-700' }, | ||
| } | ||
|
|
||
| export function InsurancePromptCard({ tripId }: InsurancePromptCardProps) { | ||
| const router = useRouter() | ||
|
Check failure on line 35 in apps/admin/src/app/trips/[id]/_components/insurance-prompt-card.tsx
|
||
| const { data } = useInsurancePrompt(tripId) | ||
|
|
||
| // Fail-open: no data yet, or nothing pending → render nothing (card hidden | ||
| // once every traveler's insurance is resolved). | ||
| if (!data || !data.anyPending) return null | ||
|
|
||
| const badge = data.urgency === 'none' ? null : URGENCY_BADGE[data.urgency] | ||
| const count = data.pendingCount | ||
| const travelerWord = count === 1 ? 'traveller' : 'travellers' | ||
|
|
||
| return ( | ||
| <div className="flex items-start justify-between gap-3 rounded-lg border border-sky-200 bg-sky-50 px-4 py-3"> | ||
| <div className="flex items-start gap-3"> | ||
| <Shield className="mt-0.5 h-5 w-5 shrink-0 text-sky-600" /> | ||
| <div className="space-y-0.5"> | ||
| <div className="flex items-center gap-2"> | ||
| <p className="text-sm font-semibold text-sky-900">Insurance not yet confirmed</p> | ||
| {badge && ( | ||
| <span | ||
| className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${badge.className}`} | ||
| > | ||
| {badge.label} | ||
| </span> | ||
| )} | ||
| </div> | ||
| <p className="text-xs text-sky-700"> | ||
| {count} {travelerWord} still {count === 1 ? 'needs' : 'need'} an insurance decision. | ||
| Confirm coverage, record their own policy, or log a declination on the Insurance tab. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| <Button | ||
| size="sm" | ||
| variant="outline" | ||
| className="shrink-0 gap-1 border-sky-300 bg-white text-sky-800 hover:bg-sky-100" | ||
| onClick={() => router.push(`/trips/${tripId}?tab=insurance`)} | ||
| > | ||
| Review insurance | ||
| <ChevronRight className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Wiring
InsurancePromptCardbreaks the existingtrip-automations.spec.tsxsuite (confirmed by CI logs).InsurancePromptCardcallsuseRouter()unconditionally, and rendering it here now throwsinvariant expected app router to be mountedin ~10 previously-passing tests, since the existing test setup doesn't provide a Next.js App Router context. This is a real regression that needs to be fixed as part of this PR (e.g. mocknext/navigation'suseRouterin the spec's render helper), not just an "informational" pipeline note.🔧 Suggested test-setup fix (in trip-automations.spec.tsx)
Also applies to: 514-516
🤖 Prompt for AI Agents
Source: Pipeline failures