setup onboarding page
This commit is contained in:
149
src/features/onboarding/steps/WelcomeStep.tsx
Normal file
149
src/features/onboarding/steps/WelcomeStep.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user