import { useState, useEffect, useRef } from 'react' import { useNavigate } from 'react-router-dom' import { ArrowRight, User, Mail, Lock, Building2, Coffee, UtensilsCrossed, Beer, ShoppingBag, CheckCircle2, ChevronRight, Hash, } from 'lucide-react' import client from '../api/client' import Button from '../ui/Button' import { LabelledInput } from '../ui/Input' // ─── Step indicator ─────────────────────────────────────────────────────────── function StepDots({ total, current }) { return (
{Array.from({ length: total }).map((_, i) => (
))}
) } // ─── Venue type selector (multi-select) ─────────────────────────────────────── const VENUE_TYPES = [ { id: 'restaurant', label: 'Restaurant', Icon: UtensilsCrossed }, { id: 'bar', label: 'Bar', Icon: Beer }, { id: 'coffee_shop', label: 'Coffee Shop', Icon: Coffee }, { id: 'fast_food', label: 'Fast Food', Icon: ShoppingBag }, ] function VenueTypeButton({ type, selected, onToggle }) { const { label, Icon } = type return ( ) } // ─── PIN pad ────────────────────────────────────────────────────────────────── const PIN_DIGITS = ['1','2','3','4','5','6','7','8','9','','0','⌫'] function PinPad({ pin, onChange }) { function press(d) { if (d === '⌫') { onChange(p => p.slice(0, -1)); return } if (d === '') return if (pin.length >= 6) return onChange(p => p + d) } return (
{Array.from({ length: 4 }).map((_, i) => (
))}
{PIN_DIGITS.map((d, i) => ( ))}
) } // ─── Steps ─────────────────────────────────────────────────────────────────── function StepWelcome({ onNext }) { return (

Welcome to Xenia POS

The complete point-of-sale system for your venue. Let's get you set up in just a few steps — it won't take long.

{[ 'Create your manager account', 'Tell us about your venue', 'You\'re ready to go', ].map((s, i) => (
{i + 1}
{s}
))}

Thank you for choosing Xenia POS.

) } function StepAccount({ data, onChange, onNext, onBack }) { const [errors, setErrors] = useState({}) // Live password match: debounce 1s after confirm changes const confirmTimer = useRef(null) function handleConfirmChange(value) { onChange('confirm', value) clearTimeout(confirmTimer.current) confirmTimer.current = setTimeout(() => { if (value && data.password && value !== data.password) { setErrors(e => ({ ...e, confirm: 'Passwords do not match' })) } else { setErrors(e => { const next = { ...e }; delete next.confirm; return next }) } }, 1000) } // Also clear the error immediately if they now match function handlePasswordChange(value) { onChange('password', value) if (data.confirm && value === data.confirm) { setErrors(e => { const next = { ...e }; delete next.confirm; return next }) } } function validate() { const e = {} if (!data.username.trim()) e.username = 'Username is required' if (!data.fullName.trim()) e.fullName = 'Full name is required' if (!data.email.trim()) e.email = 'Email is required' else if (!/\S+@\S+\.\S+/.test(data.email)) e.email = 'Enter a valid email' if (data.password.length < 6) e.password = 'At least 6 characters' if (data.password !== data.confirm) e.confirm = 'Passwords do not match' setErrors(e) return Object.keys(e).length === 0 } return (

Create your manager account

This will be the primary administrator account.

onChange('username', e.target.value)} autoComplete="off" autoFocus /> {errors.username &&

{errors.username}

}
onChange('fullName', e.target.value)} autoComplete="off" /> {errors.fullName &&

{errors.fullName}

}
onChange('email', e.target.value)} type="email" autoComplete="off" /> {errors.email &&

{errors.email}

}
handlePasswordChange(e.target.value)} type="password" autoComplete="new-password" /> {errors.password &&

{errors.password}

}
handleConfirmChange(e.target.value)} type="password" autoComplete="new-password" /> {errors.confirm &&

{errors.confirm}

}
) } function StepVenue({ data, onChange, onNext, onBack }) { const [errors, setErrors] = useState({}) function toggleType(id) { const current = data.venueTypes const next = current.includes(id) ? current.filter(v => v !== id) : [...current, id] onChange('venueTypes', next) } function validate() { const e = {} if (data.venueTypes.length === 0) e.venueType = 'Please select at least one type' if (!data.venueName.trim()) e.venueName = 'Venue name is required' setErrors(e) return Object.keys(e).length === 0 } return (

Tell us about your venue

Select all that apply — you can always change this later.

Venue type

{VENUE_TYPES.map(type => ( ))}
{errors.venueType &&

{errors.venueType}

}

Venue name

onChange('venueName', e.target.value)} autoComplete="off" /> {errors.venueName &&

{errors.venueName}

}
) } function StepPin({ pin, onChange, onNext, onBack }) { const hasPin = pin.length >= 4 return (

Set a quick-access PIN

Optional. Used to unlock the dashboard quickly when it's locked, without typing your full password.

{hasPin && (

PIN set — {pin.length} digits

)}
) } function StepDone({ onGoToLogin, loading, error }) { return (

You're all set!

Your manager account is ready. Log in and start configuring your products, tables, and staff.

{['Add your menu and products', 'Set up tables and zones', 'Add your waiters'].map((tip, i) => (
{tip}
))}
{error &&

{error}

}
) } // ─── Main wizard ────────────────────────────────────────────────────────────── // Steps: 0=welcome, 1=account, 2=venue, 3=pin, 4=done const INNER_STEPS = 3 // steps 1-3 show the dot indicator export default function SetupWizard() { const navigate = useNavigate() const [step, setStep] = useState(0) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [account, setAccount] = useState({ username: '', fullName: '', email: '', password: '', confirm: '', }) const [venue, setVenue] = useState({ venueTypes: [], venueName: '' }) const [pin, setPin] = useState('') function updateAccount(key, value) { setAccount(prev => ({ ...prev, [key]: value })) } function updateVenue(key, value) { setVenue(prev => ({ ...prev, [key]: value })) } async function handleFinish() { setLoading(true) setError('') try { await client.post('/api/setup/init', { username: account.username.trim(), password: account.password, email: account.email.trim(), full_name: account.fullName.trim(), venue_type: venue.venueTypes.join(','), venue_name: venue.venueName.trim(), pin: pin.length >= 4 ? pin : undefined, }) navigate('/login', { replace: true }) } catch (err) { setError(err.response?.data?.detail || 'Something went wrong. Please try again.') setLoading(false) } } return (
{/* Dot indicator for inner steps only */} {step >= 1 && step <= INNER_STEPS && (
)} {step === 0 && setStep(1)} />} {step === 1 && ( setStep(2)} onBack={() => setStep(0)} /> )} {step === 2 && ( setStep(3)} onBack={() => setStep(1)} /> )} {step === 3 && ( setStep(4)} onBack={() => setStep(2)} /> )} {step === 4 && ( )}

Xenia POS · First-time Setup

) }