- 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>
72 lines
3.0 KiB
TypeScript
72 lines
3.0 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Button } from './Button';
|
|
import { MousePointer2, Loader2, Check } from 'lucide-react';
|
|
|
|
interface Props {
|
|
type: string;
|
|
onComplete: () => void;
|
|
}
|
|
|
|
export const TourSimulation: React.FC<Props> = ({ type, onComplete }) => {
|
|
const [step, setStep] = useState(0);
|
|
|
|
const getSteps = () => {
|
|
if (type === 'UPLOAD') return ['Navigating to Contacts...', 'Clicking Import CSV...', 'Mapping Fields...', 'Import Confirmed!'];
|
|
if (type === 'SMS_DIY') return ['Opening Settings...', 'Buying Phone Number...', 'Verifying A2P Brand...', 'SMS Active!'];
|
|
if (type === 'EMAIL_DIY') return ['Opening Domain Settings...', 'Adding DNS Records...', 'Verifying DKIM/SPF...', 'Email Warmup Started!'];
|
|
if (type === 'CAMPAIGN_DIY') return ['Selecting Audience...', 'Choosing Template...', 'Setting Schedule...', 'Campaign Launched!'];
|
|
if (type === 'LEAD_STORE') return ['Accessing Lead Database...', 'Filtering by Criteria...', 'Selecting 50 Leads...', 'Leads Acquired!'];
|
|
return ['Processing...'];
|
|
};
|
|
|
|
const steps = getSteps();
|
|
|
|
useEffect(() => {
|
|
if (step < steps.length) {
|
|
const timer = setTimeout(() => {
|
|
setStep(prev => prev + 1);
|
|
}, 1500); // 1.5s per step
|
|
return () => clearTimeout(timer);
|
|
} else {
|
|
const timer = setTimeout(() => {
|
|
onComplete();
|
|
}, 1000);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [step, steps.length, onComplete]);
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-white/80 backdrop-blur-md animate-fadeIn">
|
|
<div className="text-center p-8">
|
|
<div className="relative w-24 h-24 mx-auto mb-6 bg-indigo-50 rounded-full flex items-center justify-center shadow-lg">
|
|
{step < steps.length ? (
|
|
<MousePointer2 className="text-indigo-600 animate-bounce" size={40} />
|
|
) : (
|
|
<Check className="text-green-600 scale-125 transition-transform" size={40} />
|
|
)}
|
|
</div>
|
|
|
|
<h2 className="text-3xl font-bold text-gray-800 mb-4">
|
|
{step < steps.length ? 'Guided Tour in Progress' : 'Setup Complete!'}
|
|
</h2>
|
|
|
|
<div className="space-y-3 min-h-[160px]">
|
|
{steps.map((text, index) => (
|
|
<div
|
|
key={index}
|
|
className={`transition-all duration-500 flex items-center justify-center gap-2
|
|
${index === step ? 'text-indigo-600 font-bold scale-110 opacity-100' : ''}
|
|
${index < step ? 'text-green-500 opacity-50' : ''}
|
|
${index > step ? 'text-gray-300 opacity-30 blur-[1px]' : ''}
|
|
`}
|
|
>
|
|
{index < step && <Check size={16} />}
|
|
{index === step && <Loader2 size={16} className="animate-spin" />}
|
|
{text}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |