// onboarding.jsx — анкета при первом входе: цель (7 вариантов из data/goals.json + своя), // уровень владения ИИ 1–5, роль, чем готов делиться. Сохраняется в app.profiles // (PUT /api/profile), в ответ — 3 рекомендованных материала по цели. const ERR_TEXT_PROFILE = { bad_goal: 'Выберите цель.', goal_custom_required: 'Опишите свою цель в паре слов.', bad_level: 'Отметьте уровень владения ИИ.', bad_role: 'Выберите роль.', }; function Onboarding({ nav }) { const cfg = CFG(); const prev = ME().profile; const [goal, setGoal] = React.useState(prev ? prev.goal : ''); const [goalCustom, setGoalCustom] = React.useState((prev && prev.goal_custom) || ''); const [level, setLevel] = React.useState(prev ? prev.ai_level : 0); const [role, setRole] = React.useState(prev ? prev.role : ''); const [roleCustom, setRoleCustom] = React.useState((prev && prev.role_custom) || ''); const [share, setShare] = React.useState(prev ? (prev.share || []) : []); const [shareCustom, setShareCustom] = React.useState((prev && prev.share_custom) || ''); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(''); const [done, setDone] = React.useState(null); const toggleShare = (id) => setShare(s => s.includes(id) ? s.filter(x => x !== id) : s.concat(id)); const submit = async (e) => { e.preventDefault(); if (busy) return; if (!goal) return setErr(ERR_TEXT_PROFILE.bad_goal); if (goal === 'other' && !goalCustom.trim()) return setErr(ERR_TEXT_PROFILE.goal_custom_required); if (!level) return setErr(ERR_TEXT_PROFILE.bad_level); if (!role) return setErr(ERR_TEXT_PROFILE.bad_role); setBusy(true); setErr(''); try { const r = await fetch('/api/profile', { method:'PUT', credentials:'same-origin', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify({ goal, goal_custom: goalCustom, ai_level: level, role, role_custom: roleCustom, share, share_custom: shareCustom }) }); const j = await r.json().catch(()=>null); if (r.ok && j && j.ok) { window.__ME = { ...ME(), profile: j.profile }; setDone(j.recommended || []); window.scrollTo(0, 0); } else setErr(ERR_TEXT_PROFILE[j && j.error] || 'Не удалось сохранить — попробуйте ещё раз.'); } catch (e2) { setErr('Сервер не ответил, попробуйте через минуту.'); } setBusy(false); }; const skip = () => { try { sessionStorage.setItem('cm_onboarding_skip', '1'); } catch (e) {} nav('home'); }; if (done) { const g = (cfg.goals || []).find(x => x.id === goal); return (
Цель — {goal === 'other' ? goalCustom : g && g.title}. Вот три материала под неё; цель можно поменять в профиле.