VenueDetailStep: move services to informational cards on left panel
- Removed service toggles from right panel (selection moves to next step) - Added informational service cards on left panel with name, description, price - Intro text: "You can choose which ones to include in the next step" - Removed VenueDetailStepValues type, values/onChange props, handleToggle - Simplified stories (no more useState for service state) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { VenueDetailStep } from './VenueDetailStep';
|
import { VenueDetailStep } from './VenueDetailStep';
|
||||||
import type { VenueDetailStepValues, VenueDetail, VenueService } from './VenueDetailStep';
|
import type { VenueDetail, VenueService } from './VenueDetailStep';
|
||||||
import { Navigation } from '../../organisms/Navigation';
|
import { Navigation } from '../../organisms/Navigation';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
@@ -57,17 +56,26 @@ const sampleVenue: VenueDetail = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sampleServices: VenueService[] = [
|
const sampleServices: VenueService[] = [
|
||||||
{ id: 'photo', name: 'Photo presentation', price: 150 },
|
{
|
||||||
{ id: 'streaming', name: 'Livestream', price: 200 },
|
id: 'photo',
|
||||||
{ id: 'recording', name: 'Recording', price: 100 },
|
name: 'Photo presentation',
|
||||||
|
description: 'Display a photo slideshow during the service',
|
||||||
|
price: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'streaming',
|
||||||
|
name: 'Livestream',
|
||||||
|
description: 'Allow family and friends to watch the service remotely',
|
||||||
|
price: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'recording',
|
||||||
|
name: 'Recording',
|
||||||
|
description: 'Receive a recording of the service to keep',
|
||||||
|
price: 100,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const defaultValues: VenueDetailStepValues = {
|
|
||||||
photoDisplay: false,
|
|
||||||
streaming: false,
|
|
||||||
recording: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─── Meta ────────────────────────────────────────────────────────────────────
|
// ─── Meta ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const meta: Meta<typeof VenueDetailStep> = {
|
const meta: Meta<typeof VenueDetailStep> = {
|
||||||
@@ -84,48 +92,15 @@ type Story = StoryObj<typeof VenueDetailStep>;
|
|||||||
|
|
||||||
// ─── Default ───────────────────────────────────────────────────────────────
|
// ─── Default ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Full venue detail with service toggles */
|
/** Full venue detail with available services */
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render: () => {
|
args: {
|
||||||
const [values, setValues] = useState<VenueDetailStepValues>({ ...defaultValues });
|
venue: sampleVenue,
|
||||||
|
onAddVenue: () => alert('Venue added!'),
|
||||||
return (
|
onBack: () => alert('Back'),
|
||||||
<VenueDetailStep
|
onSaveAndExit: () => alert('Save and exit'),
|
||||||
venue={sampleVenue}
|
services: sampleServices,
|
||||||
values={values}
|
navigation: nav,
|
||||||
onChange={setValues}
|
|
||||||
onAddVenue={() => alert('Venue added!')}
|
|
||||||
onBack={() => alert('Back')}
|
|
||||||
onSaveAndExit={() => alert('Save and exit')}
|
|
||||||
services={sampleServices}
|
|
||||||
navigation={nav}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─── With services selected ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/** Services already toggled on */
|
|
||||||
export const WithServices: Story = {
|
|
||||||
render: () => {
|
|
||||||
const [values, setValues] = useState<VenueDetailStepValues>({
|
|
||||||
photoDisplay: true,
|
|
||||||
streaming: true,
|
|
||||||
recording: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VenueDetailStep
|
|
||||||
venue={sampleVenue}
|
|
||||||
values={values}
|
|
||||||
onChange={setValues}
|
|
||||||
onAddVenue={() => alert('Venue added!')}
|
|
||||||
onBack={() => alert('Back')}
|
|
||||||
services={sampleServices}
|
|
||||||
navigation={nav}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,21 +108,13 @@ export const WithServices: Story = {
|
|||||||
|
|
||||||
/** Pre-planning variant */
|
/** Pre-planning variant */
|
||||||
export const PrePlanning: Story = {
|
export const PrePlanning: Story = {
|
||||||
render: () => {
|
args: {
|
||||||
const [values, setValues] = useState<VenueDetailStepValues>({ ...defaultValues });
|
venue: sampleVenue,
|
||||||
|
onAddVenue: () => alert('Venue selected!'),
|
||||||
return (
|
onBack: () => alert('Back'),
|
||||||
<VenueDetailStep
|
services: sampleServices,
|
||||||
venue={sampleVenue}
|
isPrePlanning: true,
|
||||||
values={values}
|
navigation: nav,
|
||||||
onChange={setValues}
|
|
||||||
onAddVenue={() => alert('Venue selected!')}
|
|
||||||
onBack={() => alert('Back')}
|
|
||||||
services={sampleServices}
|
|
||||||
isPrePlanning
|
|
||||||
navigation={nav}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,26 +122,17 @@ export const PrePlanning: Story = {
|
|||||||
|
|
||||||
/** Venue with minimal data (no features, religions, services) */
|
/** Venue with minimal data (no features, religions, services) */
|
||||||
export const Minimal: Story = {
|
export const Minimal: Story = {
|
||||||
render: () => {
|
args: {
|
||||||
const [values, setValues] = useState<VenueDetailStepValues>({ ...defaultValues });
|
venue: {
|
||||||
|
|
||||||
return (
|
|
||||||
<VenueDetailStep
|
|
||||||
venue={{
|
|
||||||
id: 'basic',
|
id: 'basic',
|
||||||
name: 'Community Hall',
|
name: 'Community Hall',
|
||||||
imageUrl:
|
imageUrl: 'https://images.unsplash.com/photo-1519167758481-83f550bb49b3?w=800&h=600&fit=crop',
|
||||||
'https://images.unsplash.com/photo-1519167758481-83f550bb49b3?w=800&h=600&fit=crop',
|
|
||||||
location: 'Parramatta',
|
location: 'Parramatta',
|
||||||
price: 500,
|
price: 500,
|
||||||
address: '10 Church Street, Parramatta NSW 2150',
|
address: '10 Church Street, Parramatta NSW 2150',
|
||||||
}}
|
},
|
||||||
values={values}
|
onAddVenue: () => alert('Venue added!'),
|
||||||
onChange={setValues}
|
onBack: () => alert('Back'),
|
||||||
onAddVenue={() => alert('Venue added!')}
|
navigation: nav,
|
||||||
onBack={() => alert('Back')}
|
|
||||||
navigation={nav}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined';
|
|||||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import { WizardLayout } from '../../templates/WizardLayout';
|
import { WizardLayout } from '../../templates/WizardLayout';
|
||||||
import { AddOnOption } from '../../molecules/AddOnOption';
|
|
||||||
import { Card } from '../../atoms/Card';
|
import { Card } from '../../atoms/Card';
|
||||||
import { Chip } from '../../atoms/Chip';
|
import { Chip } from '../../atoms/Chip';
|
||||||
import { Typography } from '../../atoms/Typography';
|
import { Typography } from '../../atoms/Typography';
|
||||||
@@ -40,21 +39,10 @@ export interface VenueService {
|
|||||||
price?: number;
|
price?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Form values for service toggles */
|
|
||||||
export interface VenueDetailStepValues {
|
|
||||||
photoDisplay: boolean;
|
|
||||||
streaming: boolean;
|
|
||||||
recording: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Props for the VenueDetailStep page component */
|
/** Props for the VenueDetailStep page component */
|
||||||
export interface VenueDetailStepProps {
|
export interface VenueDetailStepProps {
|
||||||
/** The venue to display */
|
/** The venue to display */
|
||||||
venue: VenueDetail;
|
venue: VenueDetail;
|
||||||
/** Current service toggle values */
|
|
||||||
values: VenueDetailStepValues;
|
|
||||||
/** Callback when service toggles change */
|
|
||||||
onChange: (values: VenueDetailStepValues) => void;
|
|
||||||
/** Callback when "Add Venue" is clicked */
|
/** Callback when "Add Venue" is clicked */
|
||||||
onAddVenue: () => void;
|
onAddVenue: () => void;
|
||||||
/** Callback for back navigation */
|
/** Callback for back navigation */
|
||||||
@@ -111,8 +99,6 @@ const MetaRow: React.FC<{ icon: React.ReactNode; children: React.ReactNode }> =
|
|||||||
*/
|
*/
|
||||||
export const VenueDetailStep: React.FC<VenueDetailStepProps> = ({
|
export const VenueDetailStep: React.FC<VenueDetailStepProps> = ({
|
||||||
venue,
|
venue,
|
||||||
values,
|
|
||||||
onChange,
|
|
||||||
onAddVenue,
|
onAddVenue,
|
||||||
onBack,
|
onBack,
|
||||||
onSaveAndExit,
|
onSaveAndExit,
|
||||||
@@ -125,14 +111,6 @@ export const VenueDetailStep: React.FC<VenueDetailStepProps> = ({
|
|||||||
hideHelpBar,
|
hideHelpBar,
|
||||||
sx,
|
sx,
|
||||||
}) => {
|
}) => {
|
||||||
const handleToggle = (field: keyof VenueDetailStepValues, checked: boolean) => {
|
|
||||||
const next = { ...values, [field]: checked };
|
|
||||||
if (field === 'streaming' && !checked) {
|
|
||||||
next.recording = false;
|
|
||||||
}
|
|
||||||
onChange(next);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WizardLayout
|
<WizardLayout
|
||||||
variant="detail-toggles"
|
variant="detail-toggles"
|
||||||
@@ -238,41 +216,6 @@ export const VenueDetailStep: React.FC<VenueDetailStepProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ─── Service toggles ─── */}
|
|
||||||
{services.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Divider sx={{ my: 3 }} />
|
|
||||||
<Typography variant="h5" sx={{ mb: 2 }}>
|
|
||||||
Venue services
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
||||||
<AddOnOption
|
|
||||||
name="Photo presentation"
|
|
||||||
description="Display a photo slideshow during the service"
|
|
||||||
price={services.find((s) => s.id === 'photo')?.price}
|
|
||||||
checked={values.photoDisplay}
|
|
||||||
onChange={(c) => handleToggle('photoDisplay', c)}
|
|
||||||
/>
|
|
||||||
<AddOnOption
|
|
||||||
name="Livestream"
|
|
||||||
description="Allow family and friends to watch the service remotely"
|
|
||||||
price={services.find((s) => s.id === 'streaming')?.price}
|
|
||||||
checked={values.streaming}
|
|
||||||
onChange={(c) => handleToggle('streaming', c)}
|
|
||||||
/>
|
|
||||||
{values.streaming && (
|
|
||||||
<AddOnOption
|
|
||||||
name="Recording"
|
|
||||||
description="Receive a recording of the service to keep"
|
|
||||||
price={services.find((s) => s.id === 'recording')?.price}
|
|
||||||
checked={values.recording}
|
|
||||||
onChange={(c) => handleToggle('recording', c)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -359,6 +302,47 @@ export const VenueDetailStep: React.FC<VenueDetailStepProps> = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ─── Available services (informational) ─── */}
|
||||||
|
{services.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Divider sx={{ my: 3 }} />
|
||||||
|
<Typography variant="h5" sx={{ mb: 1 }}>
|
||||||
|
Available venue services
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
This venue offers the following optional services. You can choose which ones to include
|
||||||
|
in the next step.
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||||
|
{services.map((service) => (
|
||||||
|
<Card key={service.id} variant="outlined" padding="compact">
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="label">{service.name}</Typography>
|
||||||
|
{service.description && (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{service.description}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{service.price != null && (
|
||||||
|
<Typography variant="labelSm" color="primary" sx={{ whiteSpace: 'nowrap' }}>
|
||||||
|
${service.price.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</WizardLayout>
|
</WizardLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,2 @@
|
|||||||
export { VenueDetailStep, default } from './VenueDetailStep';
|
export { VenueDetailStep, default } from './VenueDetailStep';
|
||||||
export type {
|
export type { VenueDetailStepProps, VenueDetail, VenueService } from './VenueDetailStep';
|
||||||
VenueDetailStepProps,
|
|
||||||
VenueDetailStepValues,
|
|
||||||
VenueDetail,
|
|
||||||
VenueService,
|
|
||||||
} from './VenueDetailStep';
|
|
||||||
|
|||||||
Reference in New Issue
Block a user