mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 23:39:34 +00:00
378 lines
14 KiB
TypeScript
378 lines
14 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useCallback } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { clsx } from 'clsx';
|
|
import {
|
|
UserCircle, Building2, CheckCircle2, PartyPopper,
|
|
Import, MessageCircle, GitBranch, Users,
|
|
ChevronLeft, ChevronRight, Briefcase, Sparkles,
|
|
} from 'lucide-react';
|
|
import { useI18n } from '@/i18n';
|
|
|
|
/* ---------- Types ---------- */
|
|
type Phase = 'welcome' | 'firstValue' | 'checklist';
|
|
type Role = 'salesManager' | 'salesRep' | 'executive' | 'other';
|
|
type Industry = 'realEstate' | 'automotive' | 'healthcare' | 'services' | 'other';
|
|
|
|
interface OnboardingFlowProps {
|
|
onComplete?: () => void;
|
|
className?: string;
|
|
}
|
|
|
|
/* ---------- Animation ---------- */
|
|
const slideVariants = {
|
|
enter: (dir: number) => ({ x: dir > 0 ? 80 : -80, opacity: 0 }),
|
|
center: { x: 0, opacity: 1 },
|
|
exit: (dir: number) => ({ x: dir > 0 ? -80 : 80, opacity: 0 }),
|
|
};
|
|
|
|
/* ---------- Phase 1: Welcome ---------- */
|
|
function WelcomePhase({
|
|
role,
|
|
setRole,
|
|
industry,
|
|
setIndustry,
|
|
onNext,
|
|
}: {
|
|
role: Role | null;
|
|
setRole: (r: Role) => void;
|
|
industry: Industry | null;
|
|
setIndustry: (i: Industry) => void;
|
|
onNext: () => void;
|
|
}) {
|
|
const { t, isArabic } = useI18n();
|
|
const [step, setStep] = useState<'role' | 'industry'>('role');
|
|
|
|
const roles: { key: Role; icon: typeof UserCircle }[] = [
|
|
{ key: 'salesManager', icon: UserCircle }, { key: 'salesRep', icon: Briefcase },
|
|
{ key: 'executive', icon: Building2 }, { key: 'other', icon: Users },
|
|
];
|
|
const industries: { key: Industry; label: string }[] = [
|
|
{ key: 'realEstate', label: t('onboarding.industryRealEstate') }, { key: 'automotive', label: t('onboarding.industryAutomotive') },
|
|
{ key: 'healthcare', label: t('onboarding.industryHealthcare') }, { key: 'services', label: t('onboarding.industryServices') },
|
|
{ key: 'other', label: t('onboarding.industryOther') },
|
|
];
|
|
const roleLabels: Record<Role, string> = {
|
|
salesManager: t('onboarding.roleSalesManager'), salesRep: t('onboarding.roleSalesRep'),
|
|
executive: t('onboarding.roleExecutive'), other: t('onboarding.roleOther'),
|
|
};
|
|
|
|
return (
|
|
<div className="text-center max-w-md mx-auto">
|
|
<Sparkles className="h-8 w-8 text-teal-400 mx-auto mb-4" />
|
|
<h1 className="text-2xl font-bold text-white mb-2">{t('onboarding.welcomeTitle')}</h1>
|
|
<p className="text-sm text-slate-400 mb-8">{t('onboarding.welcomeSubtitle')}</p>
|
|
|
|
<AnimatePresence mode="wait" custom={1}>
|
|
{step === 'role' ? (
|
|
<motion.div
|
|
key="role"
|
|
custom={1}
|
|
variants={slideVariants}
|
|
initial="enter"
|
|
animate="center"
|
|
exit="exit"
|
|
transition={{ duration: 0.25 }}
|
|
>
|
|
<p className="text-sm font-medium text-slate-300 mb-4">{t('onboarding.roleQuestion')}</p>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{roles.map((r) => {
|
|
const selected = role === r.key;
|
|
return (
|
|
<button key={r.key} onClick={() => { setRole(r.key); setTimeout(() => setStep('industry'), 300); }}
|
|
className={clsx('flex flex-col items-center gap-2 p-4 rounded-xl border transition-all duration-200',
|
|
selected ? 'bg-teal-500/15 border-teal-500/40 text-teal-300' : 'bg-white/5 border-white/10 text-slate-400 hover:bg-white/[0.08]')}>
|
|
<r.icon className="h-5 w-5" />
|
|
<span className="text-xs font-medium">{roleLabels[r.key]}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</motion.div>
|
|
) : (
|
|
<motion.div
|
|
key="industry"
|
|
custom={1}
|
|
variants={slideVariants}
|
|
initial="enter"
|
|
animate="center"
|
|
exit="exit"
|
|
transition={{ duration: 0.25 }}
|
|
>
|
|
<p className="text-sm font-medium text-slate-300 mb-4">{t('onboarding.industryQuestion')}</p>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{industries.map((ind) => (
|
|
<button key={ind.key} onClick={() => { setIndustry(ind.key); setTimeout(onNext, 400); }}
|
|
className={clsx('flex items-center justify-center p-3.5 rounded-xl border text-xs font-medium transition-all duration-200',
|
|
industry === ind.key ? 'bg-teal-500/15 border-teal-500/40 text-teal-300' : 'bg-white/5 border-white/10 text-slate-400 hover:bg-white/[0.08]')}>
|
|
{ind.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<button
|
|
onClick={() => setStep('role')}
|
|
className="mt-4 text-xs text-slate-500 hover:text-slate-300 transition-colors"
|
|
>
|
|
{isArabic ? (
|
|
<span className="flex items-center gap-1 justify-center"><ChevronRight className="h-3 w-3" />{t('common.back')}</span>
|
|
) : (
|
|
<span className="flex items-center gap-1 justify-center"><ChevronLeft className="h-3 w-3" />{t('common.back')}</span>
|
|
)}
|
|
</button>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ---------- Phase 2: First Value ---------- */
|
|
function FirstValuePhase({ onNext }: { onNext: () => void }) {
|
|
const { t, isArabic } = useI18n();
|
|
const [created, setCreated] = useState(false);
|
|
|
|
const handleCreate = () => {
|
|
setCreated(true);
|
|
setTimeout(onNext, 1800);
|
|
};
|
|
|
|
return (
|
|
<div className="text-center max-w-md mx-auto">
|
|
<h2 className="text-xl font-bold text-white mb-2">{t('onboarding.firstValueTitle')}</h2>
|
|
<p className="text-sm text-slate-400 mb-8">{t('onboarding.firstValueSubtitle')}</p>
|
|
|
|
<AnimatePresence mode="wait">
|
|
{!created ? (
|
|
<motion.div
|
|
key="form"
|
|
initial={{ opacity: 0, y: 12 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.95 }}
|
|
className="rounded-xl bg-white/5 border border-white/10 p-6 text-start"
|
|
>
|
|
<div className="space-y-3 mb-6">
|
|
<div>
|
|
<label className="text-[11px] text-slate-500 mb-1 block">
|
|
{isArabic ? 'اسم الصفقة' : 'Deal Name'}
|
|
</label>
|
|
<div className="rounded-lg bg-white/5 border border-white/10 px-3 py-2 text-sm text-slate-300">
|
|
{t('onboarding.sampleDealName')}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-[11px] text-slate-500 mb-1 block">
|
|
{isArabic ? 'القيمة' : 'Value'}
|
|
</label>
|
|
<div className="rounded-lg bg-white/5 border border-white/10 px-3 py-2 text-sm text-slate-300">
|
|
{isArabic ? 'ر.س' : 'SAR'} {t('onboarding.sampleDealValue')}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-[11px] text-slate-500 mb-1 block">
|
|
{isArabic ? 'جهة الاتصال' : 'Contact'}
|
|
</label>
|
|
<div className="rounded-lg bg-white/5 border border-white/10 px-3 py-2 text-sm text-slate-300">
|
|
{t('onboarding.sampleContactName')} — {t('onboarding.sampleCompany')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<motion.button
|
|
whileHover={{ scale: 1.03 }}
|
|
whileTap={{ scale: 0.97 }}
|
|
onClick={handleCreate}
|
|
className={clsx(
|
|
'w-full rounded-xl py-3 text-sm font-semibold',
|
|
'bg-gradient-to-l from-teal-500 to-emerald-600 text-white',
|
|
'hover:shadow-[0_0_24px_rgba(20,184,166,0.4)]',
|
|
'transition-shadow',
|
|
)}
|
|
>
|
|
{t('onboarding.createDeal')}
|
|
</motion.button>
|
|
</motion.div>
|
|
) : (
|
|
<motion.div
|
|
key="celebration"
|
|
initial={{ opacity: 0, scale: 0.8 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ type: 'spring', stiffness: 300, damping: 15 }}
|
|
className="py-10"
|
|
>
|
|
<motion.div
|
|
animate={{ rotate: [0, -10, 10, -5, 5, 0] }}
|
|
transition={{ duration: 0.6 }}
|
|
>
|
|
<PartyPopper className="h-14 w-14 text-amber-400 mx-auto mb-4" />
|
|
</motion.div>
|
|
<p className="text-lg font-bold text-white">{t('onboarding.celebration')}</p>
|
|
<p className="text-sm text-teal-400 mt-1">{t('onboarding.dealCreated')}</p>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ---------- Phase 3: Checklist ---------- */
|
|
interface ChecklistItem {
|
|
key: string;
|
|
label: string;
|
|
icon: typeof Import;
|
|
done: boolean;
|
|
}
|
|
|
|
function ChecklistPhase({ onComplete }: { onComplete?: () => void }) {
|
|
const { t } = useI18n();
|
|
|
|
const [items, setItems] = useState<ChecklistItem[]>([
|
|
{ key: 'import', label: t('onboarding.checkImportContacts'), icon: Import, done: false }, { key: 'whatsapp', label: t('onboarding.checkConnectWhatsApp'), icon: MessageCircle, done: false },
|
|
{ key: 'pipeline', label: t('onboarding.checkSetupPipeline'), icon: GitBranch, done: false }, { key: 'team', label: t('onboarding.checkInviteTeam'), icon: Users, done: false },
|
|
]);
|
|
|
|
const doneCount = items.filter((i) => i.done).length;
|
|
const progress = Math.round((doneCount / items.length) * 100);
|
|
|
|
const toggleItem = useCallback((key: string) => {
|
|
setItems((prev) =>
|
|
prev.map((item) =>
|
|
item.key === key ? { ...item, done: !item.done } : item,
|
|
),
|
|
);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="max-w-sm mx-auto">
|
|
<h2 className="text-lg font-bold text-white mb-1">{t('onboarding.checklistTitle')}</h2>
|
|
|
|
{/* Progress */}
|
|
<div className="flex items-center gap-3 mb-6 mt-3">
|
|
<div className="flex-1 h-2 rounded-full bg-white/10 overflow-hidden">
|
|
<motion.div
|
|
className="h-full rounded-full bg-gradient-to-l from-teal-400 to-emerald-500"
|
|
initial={{ width: 0 }}
|
|
animate={{ width: `${progress}%` }}
|
|
transition={{ duration: 0.4 }}
|
|
/>
|
|
</div>
|
|
<span className="text-xs text-slate-400 tabular-nums">
|
|
{progress}% {t('onboarding.checklistProgress')}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Items */}
|
|
<ul className="space-y-2">
|
|
{items.map((item) => {
|
|
const Icon = item.icon;
|
|
return (
|
|
<motion.li key={item.key} whileHover={{ x: 2 }} onClick={() => toggleItem(item.key)}
|
|
className={clsx('flex items-center gap-3 px-4 py-3 rounded-xl border transition-all duration-200 cursor-pointer',
|
|
item.done ? 'bg-teal-500/10 border-teal-500/25' : 'bg-white/5 border-white/10 hover:bg-white/[0.08]')}>
|
|
{item.done ? <CheckCircle2 className="h-5 w-5 text-teal-400 shrink-0" /> : <div className="h-5 w-5 rounded-full border-2 border-slate-600 shrink-0" />}
|
|
<Icon className={clsx('h-4 w-4 shrink-0', item.done ? 'text-teal-400' : 'text-slate-500')} />
|
|
<span className={clsx('text-sm flex-1', item.done ? 'text-teal-300 line-through' : 'text-slate-300')}>{item.label}</span>
|
|
</motion.li>
|
|
);
|
|
})}
|
|
</ul>
|
|
|
|
{progress === 100 && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 8 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="mt-6 text-center"
|
|
>
|
|
<motion.button
|
|
whileHover={{ scale: 1.03 }}
|
|
whileTap={{ scale: 0.97 }}
|
|
onClick={onComplete}
|
|
className={clsx(
|
|
'px-8 py-3 rounded-xl text-sm font-semibold',
|
|
'bg-gradient-to-l from-teal-500 to-emerald-600 text-white',
|
|
'hover:shadow-[0_0_24px_rgba(20,184,166,0.4)]',
|
|
'transition-shadow',
|
|
)}
|
|
>
|
|
{t('common.getStarted')}
|
|
</motion.button>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/* ---------- Main Onboarding Flow ---------- */
|
|
function OnboardingFlow({ onComplete, className }: OnboardingFlowProps) {
|
|
const { dir } = useI18n();
|
|
const [phase, setPhase] = useState<Phase>('welcome');
|
|
const [direction, setDirection] = useState(1);
|
|
const [role, setRole] = useState<Role | null>(null);
|
|
const [industry, setIndustry] = useState<Industry | null>(null);
|
|
|
|
const goTo = useCallback((next: Phase) => {
|
|
const order: Phase[] = ['welcome', 'firstValue', 'checklist'];
|
|
setDirection(order.indexOf(next) > order.indexOf(phase) ? 1 : -1);
|
|
setPhase(next);
|
|
}, [phase]);
|
|
|
|
const phases: Phase[] = ['welcome', 'firstValue', 'checklist'];
|
|
const currentIdx = phases.indexOf(phase);
|
|
|
|
return (
|
|
<div
|
|
dir={dir}
|
|
className={clsx(
|
|
'min-h-[480px] flex flex-col items-center justify-center px-4 py-12',
|
|
className,
|
|
)}
|
|
>
|
|
{/* Phase indicators */}
|
|
<div className="flex items-center gap-2 mb-10">
|
|
{phases.map((p, i) => (
|
|
<div
|
|
key={p}
|
|
className={clsx(
|
|
'h-1.5 rounded-full transition-all duration-300',
|
|
i === currentIdx ? 'w-8 bg-teal-400' : i < currentIdx ? 'w-4 bg-teal-600' : 'w-4 bg-slate-700',
|
|
)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<AnimatePresence mode="wait" custom={direction}>
|
|
<motion.div
|
|
key={phase}
|
|
custom={direction}
|
|
variants={slideVariants}
|
|
initial="enter"
|
|
animate="center"
|
|
exit="exit"
|
|
transition={{ type: 'spring', stiffness: 300, damping: 28 }}
|
|
className="w-full max-w-lg"
|
|
>
|
|
{phase === 'welcome' && (
|
|
<WelcomePhase
|
|
role={role}
|
|
setRole={setRole}
|
|
industry={industry}
|
|
setIndustry={setIndustry}
|
|
onNext={() => goTo('firstValue')}
|
|
/>
|
|
)}
|
|
{phase === 'firstValue' && (
|
|
<FirstValuePhase onNext={() => goTo('checklist')} />
|
|
)}
|
|
{phase === 'checklist' && (
|
|
<ChecklistPhase onComplete={onComplete} />
|
|
)}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export { OnboardingFlow };
|
|
export type { OnboardingFlowProps };
|