// cabinet.jsx — личный кабинет участника (CJM Насти, п. 3.1–3.3, 3.8): // Messages — «Сообщения от Николая» на главной (новость, совет по цели, продление, «Что получилось?») // TariffCard — компактный «Мой тариф» на главной; Tariff — экран ?view=tariff // ReferralCard — компактная рефералка на главной; ReferralSection — на экране тарифа // Оплат в LMS нет: все кнопки тарифа ведут в бота hakku.ai (?start=), клик пишется в /api/track. // Тексты сообщений, цены и условия — data/messages.json и data/tariffs.json на сервере. const postJSON = (url, body) => fetch(url, { method:'POST', credentials:'same-origin', headers:{ 'Content-Type':'application/json' }, body: body ? JSON.stringify(body) : undefined }) .then(r => r.json().catch(() => null).then(j => ({ ok: r.ok && j && j.ok, j }))) .catch(() => ({ ok: false, j: null })); // копирование с запасным путём для старых браузеров и http async function copyText(text) { try { await navigator.clipboard.writeText(text); return true; } catch (e) {} try { const t = document.createElement('textarea'); t.value = text; t.setAttribute('readonly', ''); t.style.position = 'fixed'; t.style.opacity = '0'; document.body.appendChild(t); t.select(); const ok = document.execCommand('copy'); t.remove(); return ok; } catch (e) { return false; } } function CopyButton({ text, label = 'Скопировать ссылку', className = 'os-btn sm', onCopy }) { const [done, setDone] = React.useState(false); const go = async () => { if (await copyText(text)) { setDone(true); onCopy && onCopy(); setTimeout(() => setDone(false), 1800); } }; return ; } /* ---- Сообщения от Николая ------------------------------------- */ function NikolayAvatar({ author, size = 40 }) { const a = author || {}; if (a.avatar) return ; return ; } // body — массив абзацев; «## …» — подзаголовок, \n — перенос строки function MsgBody({ body }) { return (
{(body || []).map((p, i) => p.startsWith('## ') ?

{p.slice(3)}

:

{p.split('\n').map((l, j) => {j ?
: null}{l}
)}

)}
); } const msgPreview = (m) => (m.body || []).filter(p => !p.startsWith('## ')).join(' ').replace(/\n/g, ' '); function AskForm({ m, onAnswered }) { const [text, setText] = React.useState(''); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(''); if (m.answer) return (
Твой ответ · команда его видит
{m.answer.answer}
); const send = async (e) => { e.preventDefault(); if (!text.trim()) return setErr('Напиши пару слов о том, что получилось.'); setBusy(true); setErr(''); const r = await postJSON(`/api/messages/${encodeURIComponent(m.id)}/answer`, { answer: text }); setBusy(false); if (r.ok) onAnswered(m.id, r.j.answer); else setErr('Не удалось отправить, попробуй ещё раз.'); }; return (