Unify PackagesStep across tiers + polish pass
Consolidate the three tier pages (PackagesStep, UnverifiedPackageT2, UnverifiedPackageT3) into a single tier-aware PackagesStep with providerTier: 'verified' | 'tier3' | 'tier2'. Copy, CTA label, price disclaimer, and itemised-unavailable state all derive from tier via an internal TIER_COPY map. Extract NearbyPackageCard as a molecule (was duplicated inline in T2 and T3). Inherits Card atom's default elevated variant so shadow matches the primary ServiceOption cards in the same column. Add showAllFromProvider variant for the "See N more packages from this provider" flow — flat list, no grouping, no secondary list, preference filter dropped. Polish pass on PackagesStep + PackageDetail: - PackageDetail header band warm → white; added card drop-shadow. - onCompare prop wire-through (button was built in but never exposed). - Price disclaimer info-box: padding/gap/line-height tuned, icon alignment fixed (mt: '3px' matches codebase convention for 16px icons paired with body2 text). - Left-column vertical rhythm: 48px gaps between provider card / subheading / list; 128px gap (Divider my: 8) between primary and secondary sections to separate groupings. - Mobile drill-in navigation via useMediaQuery + display toggles. onSelectPackage widened to accept string | null; Back button swaps to "Back to packages" when a package is selected on mobile. Scrolls to top on drill-in. - "See all" link copy: "See N more packages from this provider →" (overflow count, no provider name — sidesteps long-name wrapping). - Verified provider image: placeholder URL → real local asset (hparsonsvenue.jpg, resized 2048×1366/591KB → 640×427/52KB). Delete legacy PackageSelectPage story in PackageDetail.stories.tsx (predated the real page components). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
106
src/components/molecules/NearbyPackageCard/NearbyPackageCard.tsx
Normal file
106
src/components/molecules/NearbyPackageCard/NearbyPackageCard.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||
import StarRoundedIcon from '@mui/icons-material/StarRounded';
|
||||
import type { SxProps, Theme } from '@mui/material/styles';
|
||||
import { Card } from '../../atoms/Card';
|
||||
import { Typography } from '../../atoms/Typography';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Props for the FA NearbyPackageCard molecule */
|
||||
export interface NearbyPackageCardProps {
|
||||
/** Package display name */
|
||||
packageName: string;
|
||||
/** Package price in dollars */
|
||||
price: number;
|
||||
/** Provider display name */
|
||||
providerName: string;
|
||||
/** Provider location (suburb, city) */
|
||||
location: string;
|
||||
/** Provider rating (e.g. 4.5). Omit to hide. */
|
||||
rating?: number;
|
||||
/** Number of reviews */
|
||||
reviewCount?: number;
|
||||
/** Click handler — navigates to that provider's PackagesStep with this package loaded */
|
||||
onClick?: () => void;
|
||||
/** MUI sx prop */
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
// ─── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Compact card representing a package offered by a nearby verified provider.
|
||||
*
|
||||
* Surfaced in the "Similar packages from verified providers nearby" section
|
||||
* of the unverified-tier PackagesStep pages. Clicking the card is a route
|
||||
* change to that verified provider's PackagesStep with this package loaded.
|
||||
*
|
||||
* Composes Card + Typography.
|
||||
*/
|
||||
export const NearbyPackageCard = React.forwardRef<HTMLDivElement, NearbyPackageCardProps>(
|
||||
({ packageName, price, providerName, location, rating, reviewCount, onClick, sx }, ref) => {
|
||||
return (
|
||||
<Card
|
||||
ref={ref}
|
||||
interactive={!!onClick}
|
||||
padding="none"
|
||||
onClick={onClick}
|
||||
sx={[{ p: 'var(--fa-card-padding-compact)' }, ...(Array.isArray(sx) ? sx : [sx])]}
|
||||
>
|
||||
{/* Package name + price */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2,
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" component="span">
|
||||
{packageName}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="labelLg"
|
||||
component="span"
|
||||
color="primary"
|
||||
sx={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
${price.toLocaleString('en-AU')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Provider info */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{providerName}
|
||||
</Typography>
|
||||
{rating != null && (
|
||||
<>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
·
|
||||
</Typography>
|
||||
<StarRoundedIcon sx={{ fontSize: 14, color: 'warning.main' }} aria-hidden />
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{rating}
|
||||
{reviewCount != null ? ` (${reviewCount})` : ''}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
·
|
||||
</Typography>
|
||||
<LocationOnOutlinedIcon sx={{ fontSize: 14, color: 'text.secondary' }} aria-hidden />
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{location}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
NearbyPackageCard.displayName = 'NearbyPackageCard';
|
||||
export default NearbyPackageCard;
|
||||
Reference in New Issue
Block a user