Files
daily_console_web/src/features/onboarding/steps/WelcomeStep.tsx
2026-09-17 17:57:21 +05:30

178 lines
5.4 KiB
TypeScript

import { Button } from '@astryxdesign/core/Button';
import { Card } from '@astryxdesign/core/Card';
import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text';
import { VStack } from '@astryxdesign/core/VStack';
import { ArrowRight, Boxes, Rocket, Store, type LucideIcon } from 'lucide-react';
/**
* The first screen, and the only one whose job is not to collect anything.
*
* It exists to get somebody to begin, so it says what setup is worth and what
* it costs, and nothing else. The temptation is to explain the whole product
* here; a shopkeeper who has just signed in has no way to use any of it yet,
* and a wall of text before the first field is what makes people close the tab.
*
* The one promise made in words is the one the state layer actually keeps:
* progress is saved, so leaving is safe.
*/
/** One of the three cards under the greeting. */
export interface WelcomeBenefit {
icon: LucideIcon;
title: string;
body: string;
}
export interface WelcomeStepProps {
shopName?: string;
done: number;
total: number;
isReturning: boolean;
/**
* What setup is worth, in three cards.
*
* Given by the caller because the two roles are promised different things: a
* merchant is told about the catalogue and going on sale, a branch user about
* their own account and their shelf. The SHAPE is shared — that is the point
* of this component — and only the words differ.
*/
benefits?: readonly WelcomeBenefit[];
/** The greeting, when the default merchant wording is not the right one. */
greeting?: string;
blurb?: string;
onStart: () => void;
}
const BENEFITS = [
{
icon: Rocket,
title: 'Quick setup',
body: 'Five short steps. Most shops are through it in a few minutes.',
},
{
icon: Boxes,
title: 'Manage your catalogue',
body: 'Upload a spreadsheet or add products one at a time — whichever you have.',
},
{
icon: Store,
title: 'Ready for customers',
body: 'Finish, and your products are on sale in the app.',
},
] as const;
export function WelcomeStep({
shopName,
done,
total,
isReturning,
benefits = BENEFITS,
greeting,
blurb,
onStart,
}: WelcomeStepProps) {
const pct = total === 0 ? 0 : Math.round((done / total) * 100);
return (
<VStack gap={4} className="ob-panel">
<VStack gap={1.5} style={{ textAlign: 'center', alignItems: 'center' }}>
{/* A mark rather than an illustration. A stock drawing would be the one
thing on screen not drawn from the console's own design language. */}
<span
aria-hidden
style={{
width: 64,
height: 64,
borderRadius: 20,
display: 'grid',
placeItems: 'center',
background: 'var(--color-brand-tint)',
color: 'var(--color-brand)',
}}
>
<Store size={28} />
</span>
<Text
type="display-2"
weight="semibold"
style={{ fontFamily: 'var(--font-display)', textWrap: 'balance' }}
>
{isReturning ? 'Welcome back 👋' : (greeting ?? `Welcome${shopName ? ` to ${shopName}` : ''}! 👋`)}
</Text>
<Text
type="body"
color="secondary"
style={{ maxWidth: '46ch', lineHeight: 1.65 }}
>
{isReturning
? `You have completed ${done} of ${total} setup steps. Pick up where you left off.`
: (blurb ?? "Let's get your store ready to start selling.")}
</Text>
</VStack>
<div className="ob-benefits">
{benefits.map(({ icon: Icon, title, body }) => (
<Card key={title} padding={3}>
<VStack gap={1}>
<span style={{ color: 'var(--color-brand)' }} aria-hidden>
<Icon size={20} />
</span>
<Text type="label" size="sm" weight="semibold">
{title}
</Text>
<Text type="body" size="sm" color="secondary" style={{ lineHeight: 1.6 }}>
{body}
</Text>
</VStack>
</Card>
))}
</div>
<VStack gap={1} style={{ alignItems: 'center' }}>
<Text type="body" size="sm" color="secondary" hasTabularNumbers>
{done} of {total} steps completed
</Text>
<div
role="progressbar"
aria-valuenow={done}
aria-valuemin={0}
aria-valuemax={total}
aria-label="Setup progress"
style={{
width: 'min(320px, 100%)',
height: 6,
borderRadius: 999,
background: 'var(--color-line)',
overflow: 'hidden',
}}
>
<div
style={{
width: `${pct}%`,
height: '100%',
background: 'var(--color-brand)',
transition: 'width .35s cubic-bezier(.4,0,.2,1)',
}}
/>
</div>
</VStack>
<VStack gap={1} style={{ alignItems: 'center' }}>
<HStack justify="center">
<Button
label={isReturning ? 'Continue setup' : 'Start setup'}
variant="primary"
size="lg"
endContent={<ArrowRight size={15} />}
onClick={onStart}
/>
</HStack>
<Text type="body" size="xsm" color="secondary">
You can save your progress and continue later.
</Text>
</VStack>
</VStack>
);
}