import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { CheckCircle2, ArrowRight, ArrowLeft, Building2, UserCircle, Smartphone, SkipForward, Loader2, Eye, EyeOff } from 'lucide-react'; import { useAuth } from '../lib/auth'; import { api } from '../lib/api'; import { initMetaSDK, launchEmbeddedSignup } from '../lib/meta-signup'; import { useToast } from '../hooks/useToast'; import { logError, logWarn } from '../lib/logger'; // ─── Modes ─────────────────────────────────────────────────────────────────── const MODE_KEYS = [ { value: 'EDTECH', labelKey: 'onboarding.mode_edtech_label', descKey: 'onboarding.mode_edtech_desc' }, { value: 'CRM_MARKETING', labelKey: 'onboarding.mode_crm_label', descKey: 'onboarding.mode_crm_desc' }, { value: 'AI_AGENT', labelKey: 'onboarding.mode_ai_label', descKey: 'onboarding.mode_ai_desc' }, { value: 'CUSTOMER_SERVICE', labelKey: 'onboarding.mode_customer_service_label', descKey: 'onboarding.mode_customer_service_desc' }, ]; // ─── Slug helpers ───────────────────────────────────────────────────────────── function toSlug(name: string): string { return name .toLowerCase() .normalize('NFD').replace(/[̀-ͯ]/g, '') .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 40); } // ─── Steps ─────────────────────────────────────────────────────────────────── const STEP_KEYS = [ { id: 'org', titleKey: 'onboarding.step_org', icon: Building2 }, { id: 'admin', titleKey: 'onboarding.step_admin', icon: UserCircle }, { id: 'whatsapp', titleKey: 'onboarding.step_whatsapp', icon: Smartphone }, ]; // ─── Wizard ────────────────────────────────────────────────────────────────── export default function OnboardingWizard() { const { t } = useTranslation(); const [step, setStep] = useState(0); const [loading, setLoading] = useState(false); const [showPass, setShowPass] = useState(false); const { token } = useAuth(); const navigate = useNavigate(); const toast = useToast(); const [org, setOrg] = useState({ name: '', slug: '', slugTouched: false, mode: 'EDTECH', }); const [admin, setAdmin] = useState({ adminName: '', adminEmail: '', password: '', }); const [wa, setWa] = useState({ wabaId: '', metaBusinessId: '', accessToken: '', skip: false, }); const [tokenValidState, setTokenValidState] = useState<'idle' | 'checking' | 'valid' | 'invalid'>('idle'); const validateToken = async (tok: string) => { if (!tok.trim() || !token) return; setTokenValidState('checking'); try { const res = await api.post('/v1/organizations/whatsapp-validate-token', { token: tok }, token); setTokenValidState(res.valid ? 'valid' : 'invalid'); } catch { setTokenValidState('idle'); } }; // Auto-generate slug from name unless user edited it manually useEffect(() => { if (!org.slugTouched) { setOrg(s => ({ ...s, slug: toSlug(s.name) })); } }, [org.name, org.slugTouched]); useEffect(() => { initMetaSDK(); }, []); // ── Navigation ────────────────────────────────────────────────────────────── const canNext = () => { if (step === 0) return org.name.trim().length >= 2 && org.slug.length >= 3; if (step === 1) return admin.adminEmail.includes('@') && admin.adminName.trim().length >= 2; return true; }; const next = () => setStep(s => Math.min(s + 1, STEP_KEYS.length - 1)); const back = () => setStep(s => Math.max(s - 1, 0)); // ── Submit ────────────────────────────────────────────────────────────────── const handleSubmit = async () => { setLoading(true); try { // 1. Create organisation + admin user const result = await api.post('/v1/organizations', { name: org.name.trim(), slug: org.slug, mode: org.mode, adminName: admin.adminName.trim(), adminEmail: admin.adminEmail.trim().toLowerCase(), ...(admin.password && { password: admin.password }), }, token) as { id: string; admin: { tempPassword: string } }; const newOrgId = result.id; // 2. Connect WhatsApp if provided if (!wa.skip && wa.wabaId) { await api.post(`/v1/organizations/${newOrgId}/whatsapp-setup`, { wabaId: wa.wabaId.trim(), ...(wa.accessToken && { accessToken: wa.accessToken.trim() }), }, token); if (wa.metaBusinessId) { await api.put(`/v1/organizations/${newOrgId}`, { metaBusinessId: wa.metaBusinessId.trim(), }, token); } } navigate('/clients'); } catch (err: any) { logError(err); toast.error(err.message || t('onboarding.create_error')); } finally { setLoading(false); } }; // ── Embedded Signup ───────────────────────────────────────────────────────── const handleEmbeddedSignup = async () => { try { const result = await launchEmbeddedSignup(); setWa(s => ({ ...s, wabaId: result.waba_id, accessToken: result.code, skip: false, })); } catch (err) { logWarn('[OnboardingWizard] Facebook login failed', err); toast.error(t('onboarding.fb_error')); } }; // ── Render ────────────────────────────────────────────────────────────────── return (
{/* Step bar */}
{STEP_KEYS.map((s, i) => { const Icon = s.icon; return (
{i < step ? : }
{t(s.titleKey)}
); })}
{/* Content */}
{/* Step 0 — Organisation */} {step === 0 && (

{t('onboarding.org_title')}

{t('onboarding.org_subtitle')}

{/* Nom */}
setOrg(s => ({ ...s, name: e.target.value }))} placeholder="ex: École Polytechnique de Dakar" className="w-full px-4 py-3 rounded-xl border border-gray-200 outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition" />
{/* Slug */}
xamle.studio/ setOrg(s => ({ ...s, slug: toSlug(e.target.value), slugTouched: true }))} placeholder="ecole-poly-dakar" className="flex-1 px-3 py-3 outline-none text-sm font-mono bg-white" />

{t('onboarding.slug_hint')}

{/* Mode */}
{MODE_KEYS.map(m => ( ))}
)} {/* Step 1 — Admin */} {step === 1 && (

{t('onboarding.admin_title')}

{t('onboarding.admin_subtitle')}

setAdmin(s => ({ ...s, adminName: e.target.value }))} placeholder="Prénom Nom" className="w-full px-4 py-3 rounded-xl border border-gray-200 outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition" />
setAdmin(s => ({ ...s, adminEmail: e.target.value }))} placeholder="admin@ecole.sn" className="w-full px-4 py-3 rounded-xl border border-gray-200 outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition" />
setAdmin(s => ({ ...s, password: e.target.value }))} placeholder={t('onboarding.admin_pass_placeholder')} className="w-full px-4 py-3 pr-10 rounded-xl border border-gray-200 outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition" />

{t('onboarding.admin_pass_hint')}

)} {/* Step 2 — WhatsApp */} {step === 2 && (

{t('onboarding.wa_title')}

{t('onboarding.wa_subtitle')}

{/* Contextual help block */} {!wa.wabaId && (
{t('onboarding.whatsapp_help_title')}
WABA ID WhatsApp Manager → {t('onboarding.help_waba_id')}
Business ID Meta Business — {t('onboarding.help_business_id')}
Token {t('onboarding.help_token')}
💡 {t('onboarding.help_new_to_meta')}
)} {wa.wabaId ? ( /* Already captured via Embedded Signup */

{t('onboarding.fb_account_connected')}

WABA : {wa.wabaId}

) : (
{/* Option A — Direct */}

{t('onboarding.already_configured')}

{/* Step 1 — WABA ID */}
1
setWa(s => ({ ...s, wabaId: e.target.value }))} className="w-full border border-slate-200 rounded-xl px-3 py-2.5 text-sm font-mono outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-400" />

WhatsApp Manager → colonne "Compte WhatsApp Business"

{/* Step 2 — Business ID */}
2
setWa(s => ({ ...s, metaBusinessId: e.target.value }))} className="w-full border border-slate-200 rounded-xl px-3 py-2.5 text-sm font-mono outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-400" />

Paramètres Meta → Informations de l'entreprise

{/* Step 3 — Token */}
3
{ setWa(s => ({ ...s, accessToken: e.target.value })); setTokenValidState('idle'); }} onBlur={e => validateToken(e.target.value)} className={`w-full border rounded-xl px-3 py-2.5 text-sm font-mono outline-none focus:ring-2 pr-8 ${ tokenValidState === 'valid' ? 'border-emerald-400 focus:ring-emerald-300' : tokenValidState === 'invalid' ? 'border-red-400 focus:ring-red-300' : 'border-slate-200 focus:ring-indigo-500/20 focus:border-indigo-400' }`} /> {tokenValidState === 'checking' && ( )} {tokenValidState === 'valid' && ( )} {tokenValidState === 'invalid' && ( )}
{tokenValidState === 'valid' && (

✅ {t('onboarding.token_valid_msg')}

)} {tokenValidState === 'invalid' && (

❌ {t('onboarding.token_invalid_msg')}

)} {tokenValidState === 'idle' && (

{t('onboarding.token_idle_hint')}

)}
{/* Separator */}
ou
{/* Option B — Embedded Signup */}

{t('onboarding.new_account_via_fb')}

{t('onboarding.new_account_desc')}

)} {/* Skip */}
)}
{/* Footer */}
); }