// 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}. Вот три материала под неё; цель можно поменять в профиле.

{done.map(r => )}
); } return (
{prev ? 'Анкета участника' : 'Первый вход · 2 минуты'}

Зачем вы в сообществе?

По ответам подберём материалы, напомним о цели и подскажем, с кем из участников стоит пообщаться.

1Главная цель на ближайшие месяцы
{(cfg.goals || []).map(g => ( ))}
{goal === 'other' && ( setGoalCustom(e.target.value)} aria-label="Своя цель"/> )}
2Как вы сейчас владеете ИИ
{(cfg.levels || []).map(l => ( ))}
3Ваша роль
{(cfg.roles || []).map(r => ( ))}
{role === 'other' && ( setRoleCustom(e.target.value)} aria-label="Роль"/> )}
4Чем готовы делиться с участниками можно несколько или ничего
{(cfg.share || []).map(s => ( ))}
setShareCustom(e.target.value)} aria-label="Чем готовы делиться"/>
{err &&
{err}
}
{!prev && } {prev && }
); } window.Onboarding = Onboarding;