setup onboarding page

This commit is contained in:
2026-09-02 11:26:00 +05:30
parent b72bbd2f12
commit 7df8a49e5f
23 changed files with 2490 additions and 417 deletions

View File

@@ -0,0 +1,149 @@
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 } 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.
*/
export interface WelcomeStepProps {
shopName?: string;
done: number;
total: number;
isReturning: boolean;
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, 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 👋' : `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.`
: "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} elevation="low">
<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>
);
}