BillTracker/client/components/admin/OnboardingWizard.jsx

161 lines
6.1 KiB
JavaScript

import React, { useState } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { toast } from 'sonner';
import { api } from '@/api';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
export default function OnboardingWizard({ onComplete }) {
const [step, setStep] = useState(0);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirm, setConfirm] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleCreate = async (e) => {
e.preventDefault();
setError('');
let validationError = '';
if (password !== confirm) {
validationError = 'Passwords do not match.';
} else if (password.length < 8) {
validationError = 'Password must be at least 8 characters.';
}
if (validationError) {
setError(validationError);
toast.error(validationError);
return;
}
setLoading(true);
try {
await api.createUser({ username, password });
toast.success('User created successfully.');
onComplete();
} catch (err) {
const errorMessage = err.message || 'Failed to create user.';
setError(errorMessage);
toast.error(errorMessage);
} finally {
setLoading(false);
}
};
return (
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-64px)] px-4 py-12">
<div className="w-full max-w-md">
<div className="flex justify-center gap-2 mb-8">
{[0, 1].map(i => (
<span
key={i}
className={`h-2 rounded-full transition-all ${i === step ? 'w-6 bg-primary' : 'w-2 bg-border'}`}
/>
))}
</div>
{step === 0 && (
<Card>
<CardHeader>
<CardTitle className="text-xl">Welcome, Administrator</CardTitle>
<p className="text-sm text-muted-foreground mt-1">
Before creating your first user, please understand what your admin account can and cannot do.
</p>
</CardHeader>
<CardContent className="space-y-3">
<div className="space-y-2.5">
{[
{ can: true, text: 'Create and manage user accounts' },
{ can: true, text: 'Reset user passwords' },
{ can: true, text: 'Configure email notifications' },
{ can: true, text: 'Toggle single-user / multi-user mode' },
{ can: false, text: 'Cannot view bills or financial data' },
{ can: false, text: 'Cannot access user settings or history' },
].map(({ can, text }) => (
<div key={text} className="flex items-center gap-3 text-sm">
<span className={`shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold ${can ? 'bg-emerald-500/15 text-emerald-400' : 'bg-red-500/15 text-red-400'}`}>
{can ? '✓' : '✗'}
</span>
<span className={can ? 'text-foreground' : 'text-muted-foreground'}>{text}</span>
</div>
))}
</div>
<div className="pt-4">
<Button className="w-full" onClick={() => setStep(1)}>
Got it, create my user account
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
)}
{step === 1 && (
<Card>
<CardHeader>
<CardTitle className="text-xl">Create first user</CardTitle>
<p className="text-sm text-muted-foreground mt-1">
This account will be used to access the bill tracker.
</p>
</CardHeader>
<CardContent>
<form onSubmit={handleCreate} className="space-y-4">
<div className="space-y-1.5">
<Label htmlFor="ob-username">Username</Label>
<Input
id="ob-username"
placeholder="username"
value={username}
onChange={e => setUsername(e.target.value)}
required
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="ob-password">Password</Label>
<Input
id="ob-password"
type="password"
placeholder="Password"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="ob-confirm">Confirm password</Label>
<Input
id="ob-confirm"
type="password"
placeholder="Confirm password"
value={confirm}
onChange={e => setConfirm(e.target.value)}
required
/>
</div>
{error && (
<div className="rounded-lg border border-destructive/25 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{error}
</div>
)}
<div className="flex gap-2 pt-2">
<Button type="button" variant="outline" onClick={() => setStep(0)}>
<ChevronLeft className="h-4 w-4" />
Back
</Button>
<Button type="submit" className="flex-1" disabled={loading} aria-busy={loading}>
{loading ? 'Creating…' : 'Create User'}
</Button>
</div>
</form>
</CardContent>
</Card>
)}
</div>
</div>
);
}