From abb7f46a33c9f94644aae7c056cf1f4904b81479 Mon Sep 17 00:00:00 2001 From: Richie Date: Tue, 31 Mar 2026 17:54:33 +1100 Subject: [PATCH] New UnverifiedProviderStep page for scraped provider listings Page shown when user selects an unverified provider from ProvidersStep. Displays whatever data we have, offers enquiry CTA, recommends verified alternatives. Sections (all conditional based on available data): - Provider header: ProviderCardCompact + "Listing" info badge - What we know: definition list grid (pricing, services, area) - Enquiry CTA: warm-bg card with "Make an Enquiry" button - Verified recommendations: 2-col grid of ProviderCards 4 story variants: Default (full data), MinimalData, NoData, NoRecommendations. Uses centered-form WizardLayout. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../UnverifiedProviderStep.stories.tsx | 150 +++++++++++ .../UnverifiedProviderStep.tsx | 239 ++++++++++++++++++ .../pages/UnverifiedProviderStep/index.ts | 2 + 3 files changed, 391 insertions(+) create mode 100644 src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.stories.tsx create mode 100644 src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.tsx create mode 100644 src/components/pages/UnverifiedProviderStep/index.ts diff --git a/src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.stories.tsx b/src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.stories.tsx new file mode 100644 index 0000000..8ee3154 --- /dev/null +++ b/src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.stories.tsx @@ -0,0 +1,150 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { UnverifiedProviderStep } from './UnverifiedProviderStep'; +import type { RecommendedProvider, ProviderDetail } from './UnverifiedProviderStep'; +import { Navigation } from '../../organisms/Navigation'; +import Box from '@mui/material/Box'; + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +const FALogo = () => ( + + + + +); + +const nav = ( + } + items={[ + { label: 'FAQ', href: '/faq' }, + { label: 'Contact Us', href: '/contact' }, + { label: 'Log in', href: '/login' }, + ]} + /> +); + +const knownDetails: ProviderDetail[] = [ + { label: 'Estimated pricing', value: 'From approximately $750' }, + { label: 'Services', value: 'Cremation, Service & Cremation, Burial' }, + { label: 'Service area', value: 'Wollongong & surrounding suburbs' }, +]; + +const minimalDetails: ProviderDetail[] = [ + { label: 'Estimated pricing', value: 'From approximately $750' }, +]; + +const recommendedProviders: RecommendedProvider[] = [ + { + id: 'parsons', + name: 'H.Parsons Funeral Directors', + location: 'Wentworth, NSW', + imageUrl: 'https://placehold.co/600x200/E8E0D6/8B6F47?text=H.Parsons', + logoUrl: 'https://placehold.co/64x64/FEF9F5/BA834E?text=HP', + rating: 4.6, + reviewCount: 7, + startingPrice: 900, + }, + { + id: 'rankins', + name: 'Rankins Funeral Services', + location: 'Wollongong, NSW', + imageUrl: 'https://placehold.co/600x200/D7E1E2/4C5B6B?text=Rankins', + logoUrl: 'https://placehold.co/64x64/F2F5F6/4C5B6B?text=R', + rating: 4.8, + reviewCount: 23, + startingPrice: 1200, + }, +]; + +// ─── Meta ──────────────────────────────────────────────────────────────────── + +const meta: Meta = { + title: 'Pages/UnverifiedProviderStep', + component: UnverifiedProviderStep, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +// ─── With known details + recommendations ─────────────────────────────────── + +/** Full data — pricing, services, and verified alternatives */ +export const Default: Story = { + args: { + providerName: 'Wollongong City Funerals', + providerLocation: 'Wollongong, NSW', + providerRating: 4.2, + providerReviewCount: 15, + details: knownDetails, + recommendations: recommendedProviders, + recommendationContext: 'near Wollongong', + onEnquire: () => alert('Enquiry submitted'), + onSelectRecommendation: (id) => alert(`Navigate to provider: ${id}`), + onBack: () => alert('Back to providers'), + navigation: nav, + }, +}; + +// ─── Minimal data ─────────────────────────────────────────────────────────── + +/** Only rough pricing available — no services, no reviews */ +export const MinimalData: Story = { + args: { + providerName: 'Botanical Funerals', + providerLocation: 'Newtown, NSW', + details: minimalDetails, + recommendations: recommendedProviders, + recommendationContext: 'near Newtown', + onEnquire: () => alert('Enquiry submitted'), + onSelectRecommendation: (id) => alert(`Navigate to provider: ${id}`), + onBack: () => alert('Back to providers'), + navigation: nav, + }, +}; + +// ─── No data at all ───────────────────────────────────────────────────────── + +/** No scraped data — just the name and location */ +export const NoData: Story = { + args: { + providerName: 'Smith & Sons Funerals', + providerLocation: 'Penrith, NSW', + recommendations: recommendedProviders, + recommendationContext: 'near Penrith', + onEnquire: () => alert('Enquiry submitted'), + onSelectRecommendation: (id) => alert(`Navigate to provider: ${id}`), + onBack: () => alert('Back to providers'), + navigation: nav, + }, +}; + +// ─── No recommendations ───────────────────────────────────────────────────── + +/** No verified alternatives available for this area */ +export const NoRecommendations: Story = { + args: { + providerName: 'Wollongong City Funerals', + providerLocation: 'Wollongong, NSW', + providerRating: 4.2, + providerReviewCount: 15, + details: knownDetails, + onEnquire: () => alert('Enquiry submitted'), + onBack: () => alert('Back to providers'), + navigation: nav, + }, +}; diff --git a/src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.tsx b/src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.tsx new file mode 100644 index 0000000..ba7306a --- /dev/null +++ b/src/components/pages/UnverifiedProviderStep/UnverifiedProviderStep.tsx @@ -0,0 +1,239 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import type { SxProps, Theme } from '@mui/material/styles'; +import { WizardLayout } from '../../templates/WizardLayout'; +import { ProviderCardCompact } from '../../molecules/ProviderCardCompact'; +import { ProviderCard } from '../../molecules/ProviderCard'; +import { Badge } from '../../atoms/Badge'; +import { Button } from '../../atoms/Button'; +import { Card } from '../../atoms/Card'; +import { Typography } from '../../atoms/Typography'; +import { Divider } from '../../atoms/Divider'; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +/** A piece of known information about the unverified provider */ +export interface ProviderDetail { + /** Label (e.g. "Estimated pricing") */ + label: string; + /** Value (e.g. "From approximately $750") */ + value: string; +} + +/** A verified provider recommendation */ +export interface RecommendedProvider { + id: string; + name: string; + location: string; + imageUrl?: string; + logoUrl?: string; + rating?: number; + reviewCount?: number; + startingPrice?: number; +} + +/** Props for the UnverifiedProviderStep page */ +export interface UnverifiedProviderStepProps { + /** Provider display name */ + providerName: string; + /** Provider location */ + providerLocation: string; + /** Average rating (if available from reviews) */ + providerRating?: number; + /** Number of reviews */ + providerReviewCount?: number; + /** Known details — only sections with data are rendered */ + details?: ProviderDetail[]; + /** Callback when "Make an Enquiry" is clicked */ + onEnquire: () => void; + /** Whether the enquiry is being submitted */ + enquiryLoading?: boolean; + /** Verified provider recommendations matching the user's search */ + recommendations?: RecommendedProvider[]; + /** Label for the recommendations section (e.g. "near Wollongong") */ + recommendationContext?: string; + /** Callback when a recommended provider is clicked */ + onSelectRecommendation?: (id: string) => void; + /** Callback for back navigation */ + onBack: () => void; + /** Navigation bar */ + navigation?: React.ReactNode; + /** Progress stepper */ + progressStepper?: React.ReactNode; + /** Running total widget */ + runningTotal?: React.ReactNode; + /** MUI sx prop */ + sx?: SxProps; +} + +// ─── Component ─────────────────────────────────────────────────────────────── + +/** + * Unverified provider detail page for the FA arrangement wizard. + * + * Shown when a user selects an unverified (scraped) provider from + * the ProvidersStep. Displays whatever information we have, offers + * an enquiry CTA, and recommends verified alternatives. + * + * Uses centered-form layout — single column, focused experience. + * No package selection, no arrangement flow. + * + * Pure presentation component — props in, callbacks out. + */ +export const UnverifiedProviderStep: React.FC = ({ + providerName, + providerLocation, + providerRating, + providerReviewCount, + details = [], + onEnquire, + enquiryLoading = false, + recommendations = [], + recommendationContext, + onSelectRecommendation, + onBack, + navigation, + progressStepper, + runningTotal, + sx, +}) => { + return ( + + {/* ── Provider header ── */} + + + + }> + Listing + + + + + {/* ── Available information ── */} + {details.length > 0 && ( + + + What we know + + + {details.map((detail) => ( + + + {detail.label} + + + {detail.value} + + + ))} + + + Based on publicly available information. Pricing and services may vary. + + + )} + + {/* ── Enquiry CTA ── */} + + + Interested in {providerName}? + + + We’ll pass your details along so they can reach out to you directly. No commitment + required. + + + + + {/* ── Verified recommendations ── */} + {recommendations.length > 0 && ( + <> + + + + + Verified providers{recommendationContext ? ` ${recommendationContext}` : ''} + + + Full online arrangement with transparent pricing + + + + {recommendations.map((provider) => ( + onSelectRecommendation(provider.id) : undefined + } + /> + ))} + + + + )} + + ); +}; + +UnverifiedProviderStep.displayName = 'UnverifiedProviderStep'; +export default UnverifiedProviderStep; diff --git a/src/components/pages/UnverifiedProviderStep/index.ts b/src/components/pages/UnverifiedProviderStep/index.ts new file mode 100644 index 0000000..c70b088 --- /dev/null +++ b/src/components/pages/UnverifiedProviderStep/index.ts @@ -0,0 +1,2 @@ +export { default } from './UnverifiedProviderStep'; +export * from './UnverifiedProviderStep';