- 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>
211 lines
7.2 KiB
TypeScript
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>
|
|
);
|
|
}
|