// ============================================================ // FINAFAMILY — Onboarding Wizard (5 passos) // ============================================================ const { useState: useStateOB } = React; // ── Step progress indicator ──────────────────────────────── const OB_STEPS = [ { num: 1, label: 'Sua Conta' }, { num: 2, label: 'Família' }, { num: 3, label: 'Rendas' }, { num: 4, label: 'Pagamentos' }, { num: 5, label: 'Dia a Dia' }, ]; function OBProgress({ current }) { return (
{OB_STEPS.map((s, i) => { const done = current > s.num; const active = current === s.num; return ( {i > 0 && (
)}
{done ? : {s.num} }
{s.label}
); })}
); } // ── Step 1: Account ──────────────────────────────────────── function OBStepAccount({ account, set, error }) { return (
Crie sua conta
Você será o administrador principal da família
set('name', e.target.value)} placeholder="Ex: Carlos Silva" /> set('email', e.target.value)} placeholder="carlos@familia.com" />
set('password', e.target.value)} placeholder="Mínimo 8 caracteres" /> set('confirm', e.target.value)} placeholder="Repita a senha" />
{account.password.length > 0 && (
{[ { ok: account.password.length >= 8, label: '8+ caracteres' }, { ok: /[A-Z]/.test(account.password), label: 'Maiúscula' }, { ok: /[0-9]/.test(account.password), label: 'Número' }, { ok: account.password === account.confirm && account.confirm.length > 0, label: 'Coincidem' }, ].map(r => (
{r.label}
))}
)} {error && (
{error}
)}
); } // ── Step 2: Members ──────────────────────────────────────── function OBStepMembers({ masterMember, extras, setExtras }) { function addExtra(role) { setExtras(prev => [...prev, { name: '', role, email: '', password: '', showPass: false, _id: Date.now() }]); } function updExtra(idx, k, v) { setExtras(prev => prev.map((m, i) => i === idx ? { ...m, [k]: v } : m)); } function delExtra(idx) { setExtras(prev => prev.filter((_, i) => i !== idx)); } const hasMaster2 = extras.some(m => m.role === 'MASTER2'); return (
Quem faz parte da família?
Adicione os membros que vão usar o Finafamily
{/* Master 1 — non-removable */} {masterMember && (
{masterMember.name}
Master 1 — Administrador
)} {/* Extra members */} {extras.map((m, idx) => (
{m.role === 'MASTER2' ? '👫 Cônjuge / Master 2' : '👶 Filho(a)'}
updExtra(idx, 'name', e.target.value)} placeholder="Nome" /> updExtra(idx, 'email', e.target.value)} placeholder="email@exemplo.com" /> updExtra(idx, 'password', e.target.value)} placeholder="Mínimo 8 caracteres" />
{!m.email && !m.password && (
💡 Sem e-mail/senha, o acesso poderá ser configurado depois em Configurações → Membros
)}
))} {/* Add buttons */}
{!hasMaster2 && ( )}
Pode pular — adicione membros depois em Configurações
); } // ── Step 3: Incomes ──────────────────────────────────────── function OBStepIncomes({ allMembers, incomes, setIncomes, incomeSources }) { function addInc(memberId) { setIncomes(prev => [...prev, { memberId, desc: '', value: '', source: incomeSources[0]?.key || 'salary', recurrence: 'monthly', _id: Date.now() }]); } function updInc(idx, k, v) { setIncomes(prev => prev.map((i, j) => j === idx ? { ...i, [k]: v } : i)); } function delInc(idx) { setIncomes(prev => prev.filter((_, i) => i !== idx)); } return (
Fontes de renda
Cadastre a renda mensal de cada membro
{incomes.map((inc, idx) => { const member = allMembers.find(m => m.id === inc.memberId); return (
{member && } {member?.name?.split(' ')[0] || 'Membro'}
updInc(idx, 'desc', e.target.value)} placeholder="Ex: Salário maio" /> updInc(idx, 'value', e.target.value)} placeholder="0,00" />
); })}
{allMembers.map(m => ( ))}
Pode pular — cadastre depois em Receitas
); } // ── Step 4: Payments ─────────────────────────────────────── function OBStepPayments({ payments, setPayments }) { function addPM() { setPayments(prev => [...prev, { name: '', type: 'credit', bank: '', limit: '', closingDay: '', dueDay: '', _id: Date.now() }]); } function updPM(idx, k, v) { setPayments(prev => prev.map((p, i) => i === idx ? { ...p, [k]: v } : p)); } function delPM(idx) { setPayments(prev => prev.filter((_, i) => i !== idx)); } return (
Meios de pagamento
Cartões e contas que você usa no dia a dia
{payments.map((pm, idx) => (
updPM(idx, 'name', e.target.value)} placeholder="Ex: Nubank" /> updPM(idx, 'bank', e.target.value)} placeholder="Ex: Itaú Visa" /> {pm.type === 'credit' && ( updPM(idx, 'limit', e.target.value)} placeholder="0,00" /> )} {pm.type === 'credit' && ( <> updPM(idx, 'closingDay', e.target.value)} placeholder="Ex: 5" /> updPM(idx, 'dueDay', e.target.value)} placeholder="Ex: 15" /> )}
))}
Pode pular — adicione depois em Configurações → Pagamentos
); } // ── Step 5: Dia a Dia ────────────────────────────────────── function OBStepDiaDia({ allMembers, diaDiaLimits, setDiaDiaLimits }) { return (
Limite Dia a Dia
Quanto cada membro pode gastar em pequenas despesas por mês?
{allMembers.map(m => (
{m.name.split(' ')[0]}
{m.role}
R$ setDiaDiaLimits(prev => ({ ...prev, [m.id]: e.target.value }))} placeholder="300" style={{ width: 76, padding: '8px 10px', background: 'var(--surface-1)', border: '1px solid var(--border)', borderRadius: 8, color: 'var(--text-1)', fontSize: 14, fontWeight: 700, outline: 'none', textAlign: 'right', boxSizing: 'border-box' }} /> /mês
))}
O Dia a Dia é um limite para pequenos gastos não planejados — lanches, corridas de app, compras rápidas. Ideal para manter o controle sem complicar.
); } // ── Main Onboarding component ────────────────────────────── function Onboarding({ onComplete, onGoToLogin }) { const { register, addMember, addIncome, addPaymentMethod, updateMember, members: ctxMembers, incomeSources, loadData } = useApp(); const [step, setStep] = useStateOB(0); // 0 = welcome, 1-5 = wizard const [saving, setSaving] = useStateOB(false); const [error, setError] = useStateOB(''); // Step 1 const [account, setAccount] = useStateOB({ name: '', email: '', password: '', confirm: '' }); function setAcct(k, v) { setAccount(f => ({ ...f, [k]: v })); setError(''); } // Step 2 const [masterMember, setMasterMember] = useStateOB(null); const [extras, setExtras] = useStateOB([]); // Step 3 const [incomes, setIncomes] = useStateOB([]); // Step 4 const [payments, setPayments] = useStateOB([]); // Step 5 const [diaDiaLimits, setDiaDiaLimits] = useStateOB({}); // All members after registration const allMembers = ctxMembers.length > 0 ? ctxMembers : (masterMember ? [masterMember, ...extras.filter(m => m.name.trim()).map((m, i) => ({ ...m, id: 'tmp_' + i }))] : []); // ── Navigation ───────────────────────────────────────── async function handleNext() { setError(''); if (step === 0) { setStep(1); return; } // ── Step 1: Register ────────────────────────────────── if (step === 1) { if (!account.name.trim()) { setError('Informe seu nome completo'); return; } if (!account.email.trim()) { setError('Informe seu e-mail'); return; } if (account.password.length < 8) { setError('A senha deve ter no mínimo 8 caracteres'); return; } if (account.password !== account.confirm) { setError('As senhas não conferem'); return; } setSaving(true); try { const member = await register(account.name.trim(), account.email.trim(), account.password); setMasterMember(member); setDiaDiaLimits({ [member.id]: '300' }); setStep(2); } catch (err) { // Erros específicos da API if (err.status === 409) { setError('Este e-mail já está cadastrado. Volte e faça login.'); } else if (err.status === 400 && err.message) { setError(err.message); } else { setError(err.message || 'Erro ao criar conta. Tente novamente.'); } } finally { setSaving(false); } return; } // ── Step 2: Add members ─────────────────────────────── if (step === 2) { setSaving(true); try { for (const m of extras.filter(m => m.name.trim())) { const created = await addMember({ name: m.name.trim(), role: m.role, email: m.email || undefined, password: m.password && m.password.length >= 8 ? m.password : undefined, }); setDiaDiaLimits(prev => ({ ...prev, [created.id]: '200' })); } await loadData(new Date().getFullYear(), new Date().getMonth() + 1); setStep(3); } catch (err) { setError(err.message || 'Erro ao salvar membros'); } finally { setSaving(false); } return; } // ── Step 3: Incomes ─────────────────────────────────── if (step === 3) { setSaving(true); try { const today = todayLocalISO(); for (const inc of incomes.filter(i => i.desc.trim() && i.value)) { await addIncome({ desc: inc.desc.trim(), value: parseFloat(inc.value), date: today, source: inc.source || 'salary', recurrence: inc.recurrence || 'monthly', memberId: inc.memberId, status: 'received' }); } } catch { /* continue */ } setSaving(false); setStep(4); return; } // ── Step 4: Payments ────────────────────────────────── if (step === 4) { setSaving(true); try { for (const pm of payments.filter(p => p.name.trim())) { await addPaymentMethod({ name: pm.name.trim(), type: pm.type || 'credit', bank: pm.bank || '', limit: pm.limit ? parseFloat(pm.limit) : undefined, closingDay: pm.closingDay ? parseInt(pm.closingDay) : undefined, dueDay: pm.dueDay ? parseInt(pm.dueDay) : undefined, memberId: masterMember?.id || (ctxMembers[0]?.id || ''), }); } } catch { /* continue */ } setSaving(false); setStep(5); return; } // ── Step 5: Dia a Dia + Finish ──────────────────────── if (step === 5) { setSaving(true); try { for (const [memberId, limit] of Object.entries(diaDiaLimits)) { if (limit) await updateMember(memberId, { diaDiaLimit: parseFloat(limit) }); } await loadData(new Date().getFullYear(), new Date().getMonth() + 1); onComplete(); } catch { onComplete(); } finally { setSaving(false); } } } function handleBack() { setError(''); if (step > 1) setStep(s => s - 1); else if (step === 1) setStep(0); } // ── Layout helpers ────────────────────────────────────── const outerStyle = { minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--canvas-bg)', padding: '20px', boxSizing: 'border-box', }; const cardStyle = { width: '100%', maxWidth: 520, background: 'var(--card-bg)', border: '1px solid var(--border)', borderRadius: 18, padding: 28, boxSizing: 'border-box', }; // ── STEP 0: Welcome ───────────────────────────────────── if (step === 0) { return (
{/* Logo */}
Finafamily.
Suas finanças. Sua família. Em ordem.
Organize receitas, despesas, investimentos e limites de gastos para toda a família — num só lugar.
{/* Feature grid */}
{[ ['LayoutDashboard', 'Dashboard familiar', 'Visão completa em tempo real'], ['Wallet', 'Controle de despesas','Compartilhadas e parceladas'], ['TrendingUp', 'Investimentos', 'Acompanhe sua carteira'], ['Zap', 'Dia a Dia', 'Limite de gastos diários'], ].map(([icon, title, desc]) => (
{title}
{desc}
))}
); } // ── Steps 1-5 ────────────────────────────────────────── return (
{/* Mini header */}
Finafamily.
{/* Step content */} {step === 1 && } {step === 2 && } {step === 3 && } {step === 4 && } {step === 5 && } {/* General error (non-step-1) */} {error && step !== 1 && (
{error}
)} {/* Navigation */}
{step >= 1 && ( )}
{step === 1 && (
)}
); } Object.assign(window, { Onboarding });