nitrokite/frontend/src/pages/Onboarding.tsx
Farid Siddiqi a1360919f0
Some checks failed
Build and Deploy API Server & Frontend / deploy (push) Blocked by required conditions
Build and Deploy API Server & Frontend / build-backend (push) Has been cancelled
Build and Deploy API Server & Frontend / build-frontend (push) Has been cancelled
Add frontend React dashboard with CI/CD deployment pipeline
2025-12-25 22:08:03 -08:00

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>
);
}