219 lines
7.2 KiB
TypeScript
219 lines
7.2 KiB
TypeScript
import { useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { z } from 'zod';
|
|
import { useOnboarding } from '@/hooks/useDashboard';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { useNavigate } from '@tanstack/react-router';
|
|
|
|
const onboardingSchema = z.object({
|
|
businessName: z.string().min(2, 'Business name must be at least 2 characters'),
|
|
industry: z.string().min(2, 'Industry is required'),
|
|
website: z.string().url().optional().or(z.literal('')),
|
|
});
|
|
|
|
type OnboardingFormData = z.infer<typeof onboardingSchema>;
|
|
|
|
export function OnboardingPage() {
|
|
const [step, setStep] = useState(1);
|
|
const [selectedGoals, setSelectedGoals] = useState<string[]>([]);
|
|
const [selectedServices, setSelectedServices] = useState<string[]>([]);
|
|
const navigate = useNavigate();
|
|
const { mutate: submitOnboarding, isPending } = useOnboarding();
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
} = useForm<OnboardingFormData>({
|
|
resolver: zodResolver(onboardingSchema),
|
|
});
|
|
|
|
const goals = [
|
|
'Increase website traffic',
|
|
'Improve SEO ranking',
|
|
'Generate more leads',
|
|
'Manage online reputation',
|
|
'Grow social media presence',
|
|
'Streamline bookings',
|
|
];
|
|
|
|
const services = [
|
|
'Website Creation',
|
|
'SEO Tracking',
|
|
'Online Booking',
|
|
'Social Media Content',
|
|
'Review Management',
|
|
'Customer Feedback',
|
|
];
|
|
|
|
const toggleSelection = (item: string, list: string[], setter: (val: string[]) => void) => {
|
|
if (list.includes(item)) {
|
|
setter(list.filter((i) => i !== item));
|
|
} else {
|
|
setter([...list, item]);
|
|
}
|
|
};
|
|
|
|
const onSubmit = (data: OnboardingFormData) => {
|
|
submitOnboarding(
|
|
{
|
|
...data,
|
|
goals: selectedGoals,
|
|
services: selectedServices,
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
navigate({ to: '/dashboard' });
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
<Card className="w-full max-w-2xl">
|
|
<CardHeader>
|
|
<CardTitle>Welcome to Nitrokite! 🚀</CardTitle>
|
|
<CardDescription>
|
|
Let's get your business set up in just a few steps
|
|
</CardDescription>
|
|
<div className="mt-4 flex gap-2">
|
|
{[1, 2, 3].map((s) => (
|
|
<div
|
|
key={s}
|
|
className={`h-2 flex-1 rounded-full ${
|
|
s <= step ? 'bg-primary' : 'bg-muted'
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{step === 1 && (
|
|
<form onSubmit={handleSubmit(() => setStep(2))} className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="businessName">Business Name</Label>
|
|
<Input
|
|
id="businessName"
|
|
{...register('businessName')}
|
|
placeholder="Enter your business name"
|
|
/>
|
|
{errors.businessName && (
|
|
<p className="text-sm text-destructive mt-1">
|
|
{errors.businessName.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="industry">Industry</Label>
|
|
<Input
|
|
id="industry"
|
|
{...register('industry')}
|
|
placeholder="e.g., Restaurant, Retail, Services"
|
|
/>
|
|
{errors.industry && (
|
|
<p className="text-sm text-destructive mt-1">
|
|
{errors.industry.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="website">Website (optional)</Label>
|
|
<Input
|
|
id="website"
|
|
{...register('website')}
|
|
placeholder="https://yourbusiness.com"
|
|
/>
|
|
{errors.website && (
|
|
<p className="text-sm text-destructive mt-1">
|
|
{errors.website.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<Button type="submit" className="w-full">
|
|
Continue
|
|
</Button>
|
|
</form>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-3">What are your goals?</h3>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{goals.map((goal) => (
|
|
<button
|
|
key={goal}
|
|
type="button"
|
|
onClick={() => toggleSelection(goal, selectedGoals, setSelectedGoals)}
|
|
className={`p-3 rounded-lg border text-sm text-left transition-colors ${
|
|
selectedGoals.includes(goal)
|
|
? 'bg-primary text-primary-foreground border-primary'
|
|
: 'hover:bg-accent'
|
|
}`}
|
|
>
|
|
{goal}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" onClick={() => setStep(1)} className="flex-1">
|
|
Back
|
|
</Button>
|
|
<Button onClick={() => setStep(3)} className="flex-1">
|
|
Continue
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-3">
|
|
Which services are you interested in?
|
|
</h3>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{services.map((service) => (
|
|
<button
|
|
key={service}
|
|
type="button"
|
|
onClick={() =>
|
|
toggleSelection(service, selectedServices, setSelectedServices)
|
|
}
|
|
className={`p-3 rounded-lg border text-sm text-left transition-colors ${
|
|
selectedServices.includes(service)
|
|
? 'bg-primary text-primary-foreground border-primary'
|
|
: 'hover:bg-accent'
|
|
}`}
|
|
>
|
|
{service}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" onClick={() => setStep(2)} className="flex-1">
|
|
Back
|
|
</Button>
|
|
<Button
|
|
onClick={handleSubmit(onSubmit)}
|
|
disabled={isPending}
|
|
className="flex-1"
|
|
>
|
|
{isPending ? 'Setting up...' : 'Complete Setup'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|