BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

211 lines
7.2 KiB
TypeScript

"use client";
import { useState } from "react";
import { ArrowRight, ArrowLeft, Check, Target, Users, Zap, BarChart3 } from "lucide-react";
const steps = [
{
id: 1,
title: "What are your goals?",
description: "Select the goals that matter most to you",
},
{
id: 2,
title: "About your business",
description: "Help us customize your experience",
},
{
id: 3,
title: "Communication channels",
description: "Choose how you want to reach your leads",
},
];
const goals = [
{ id: "seller-leads", label: "Generate seller leads", icon: Target },
{ id: "buyer-leads", label: "Generate buyer leads", icon: Users },
{ id: "automation", label: "Automate follow-ups", icon: Zap },
{ id: "analytics", label: "Track metrics", icon: BarChart3 },
];
export default function OnboardingPage() {
const [currentStep, setCurrentStep] = useState(1);
const [selectedGoals, setSelectedGoals] = useState<string[]>([]);
const toggleGoal = (goalId: string) => {
setSelectedGoals((prev) =>
prev.includes(goalId)
? prev.filter((id) => id !== goalId)
: [...prev, goalId]
);
};
return (
<div className="clay-card max-w-2xl mx-auto">
{/* Progress */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
{steps.map((step, index) => (
<div key={step.id} className="flex items-center">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center font-semibold ${
currentStep > step.id
? "bg-green-500 text-white"
: currentStep === step.id
? "bg-primary-600 text-white"
: "bg-slate-200 text-slate-500"
}`}
>
{currentStep > step.id ? <Check size={20} /> : step.id}
</div>
{index < steps.length - 1 && (
<div
className={`w-16 md:w-24 h-1 mx-2 ${
currentStep > step.id ? "bg-green-500" : "bg-slate-200"
}`}
/>
)}
</div>
))}
</div>
<div className="text-center">
<h2 className="text-xl font-bold text-slate-900">
{steps[currentStep - 1].title}
</h2>
<p className="text-slate-600 mt-1">
{steps[currentStep - 1].description}
</p>
</div>
</div>
{/* Step Content */}
{currentStep === 1 && (
<div className="space-y-4">
{goals.map((goal) => {
const Icon = goal.icon;
const isSelected = selectedGoals.includes(goal.id);
return (
<button
key={goal.id}
onClick={() => toggleGoal(goal.id)}
className={`w-full p-4 rounded-xl border-2 flex items-center gap-4 transition-all ${
isSelected
? "border-primary-500 bg-primary-50"
: "border-slate-200 hover:border-slate-300"
}`}
>
<div
className={`p-3 rounded-xl ${
isSelected ? "bg-primary-100" : "bg-slate-100"
}`}
>
<Icon
className={isSelected ? "text-primary-600" : "text-slate-500"}
size={24}
/>
</div>
<span
className={`font-medium ${
isSelected ? "text-primary-900" : "text-slate-700"
}`}
>
{goal.label}
</span>
{isSelected && (
<Check className="ml-auto text-primary-600" size={20} />
)}
</button>
);
})}
</div>
)}
{currentStep === 2 && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
How many deals do you close per month?
</label>
<select className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500">
<option>1-5 deals</option>
<option>6-10 deals</option>
<option>11-20 deals</option>
<option>20+ deals</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Team size
</label>
<select className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500">
<option>Just me</option>
<option>2-5 people</option>
<option>6-10 people</option>
<option>10+ people</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Primary market
</label>
<select className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500">
<option>Office</option>
<option>Retail</option>
<option>Industrial</option>
<option>Multifamily</option>
<option>Mixed Use</option>
</select>
</div>
</div>
)}
{currentStep === 3 && (
<div className="space-y-4">
<p className="text-slate-600 mb-4">
Select the channels you want to use for outreach:
</p>
{[
{ id: "email", label: "Email", description: "Send automated email campaigns" },
{ id: "sms", label: "SMS", description: "Text message follow-ups" },
{ id: "phone", label: "Phone", description: "Call tracking and logging" },
].map((channel) => (
<label
key={channel.id}
className="flex items-start gap-4 p-4 rounded-xl border border-slate-200 hover:border-slate-300 cursor-pointer"
>
<input
type="checkbox"
className="w-5 h-5 mt-0.5 rounded border-slate-300 text-primary-600 focus:ring-primary-500"
/>
<div>
<span className="font-medium text-slate-900">{channel.label}</span>
<p className="text-sm text-slate-500">{channel.description}</p>
</div>
</label>
))}
</div>
)}
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={() => setCurrentStep((prev) => Math.max(1, prev - 1))}
className={`btn-secondary flex items-center gap-2 ${
currentStep === 1 ? "invisible" : ""
}`}
>
<ArrowLeft size={18} />
Back
</button>
<button
onClick={() => setCurrentStep((prev) => Math.min(3, prev + 1))}
className="btn-primary flex items-center gap-2"
>
{currentStep === 3 ? "Finish Setup" : "Continue"}
<ArrowRight size={18} />
</button>
</div>
</div>
);
}