// Send-gift flow — two-step: Select gifts → Recipient → Toast. function SendSelectSheet({ initialIds, gifts, onCancel, onNext }) { const all = gifts || []; // реальные held-подарки (проброс из app.jsx) const [selected, setSelected] = React.useState(() => new Set(initialIds || [])); const [query, setQuery] = React.useState(''); const closeAnim = React.useContext(SheetCloseContext); // Snapshot the initially-selected gifts so the ordering stays stable while // the user toggles inside the picker — they sort to the top once, on open, // not jumping around with every tap. const initialOrder = React.useRef(new Set(initialIds || [])); const items = React.useMemo(() => { const q = query.trim().toLowerCase(); const list = all.filter(g => !q || g.name.toLowerCase().includes(q)); return list.slice().sort((a, b) => { const sa = initialOrder.current.has(a.id) ? 0 : 1; const sb = initialOrder.current.has(b.id) ? 0 : 1; return sa - sb; }); }, [query, all]); const allSelected = items.length > 0 && items.every(g => selected.has(g.id)); const count = selected.size; const toggle = (id) => setSelected(prev => { const s = new Set(prev); s.has(id) ? s.delete(id) : s.add(id); return s; }); const toggleAll = () => setSelected(prev => { if (allSelected) { const s = new Set(prev); items.forEach(g => s.delete(g.id)); return s; } const s = new Set(prev); items.forEach(g => s.add(g.id)); return s; }); return ( <>
Выберите подарок
setQuery(e.target.value)} style={{ flex: 1, background: 'transparent', border: 0, outline: 0, color: 'var(--text)', fontSize: 14 }}/>
Выбрать все
· {count}
{items.map(g => { const on = selected.has(g.id); return (
toggle(g.id)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 10, borderRadius: 16, background: 'var(--bg-input)', border: on ? '0.5px solid var(--accent)' : '0.5px solid var(--border)', boxShadow: on ? '0 0 0 1px var(--accent), 0 4px 14px var(--accent-glow)' : 'none', transition: 'border-color 0.15s, box-shadow 0.15s', cursor: 'pointer', }}>
{g.name}
#{g.num}
{fmtRub(g.rub)}
); })}
); } function SendRecipientSheet({ gifts: initialGifts, onBack, onSend, onAddMore }) { const [gifts, setGifts] = React.useState(initialGifts); const [username, setUsername] = React.useState(''); const [anon, setAnon] = React.useState(false); const [sending, setSending] = React.useState(false); const [sendErr, setSendErr] = React.useState(null); const closeAnim = React.useContext(SheetCloseContext); const total = gifts.reduce((s, g) => s + g.rub, 0); // РЕАЛЬНАЯ отправка: POST /me/gifts/{order_id}/send по одному на подарок. const doSend = async () => { const recip = username.trim(); if (!recip || gifts.length === 0 || sending) return; setSending(true); setSendErr(null); try { const DS = window.DataSource; if (DS && DS.sendGift) { for (const g of gifts) { await DS.sendGift(g.order_id || g.id, recip, anon); // anonymous реально уходит на бэк } } else if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData) { throw new Error('DataSource unavailable'); // прод в Telegram: НЕ притворяемся успехом } else { await new Promise(r => setTimeout(r, 600)); // дизайн-симулятор в браузере } onSend({ username: recip, anon, count: gifts.length }); } catch (e) { setSendErr(window.friendlyError ? window.friendlyError(e, 'send') : 'Не удалось отправить — попробуй через минуту'); setSending(false); } }; const removeGift = (id) => setGifts(prev => prev.filter(g => g.id !== id)); // Hand the current id set up to the parent on navigation so the picker // shows what's still in the recipient bundle (and removals stick). const currentIds = gifts.map(g => g.id); return ( <>
Получатель
Введи только логин Telegram
Выбраны подарки
{fmtRub(total)} · {gifts.length} шт.
{gifts.map(g => (
removeGift(g.id)} style={{ position: 'absolute', top: -4, right: -4, width: 22, height: 22, borderRadius: '50%', background: 'rgba(0,0,0,0.6)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', border: '1.5px solid var(--bg-app)', }}>
#{g.num}
))}
onAddMore(currentIds)} style={{ width: 72, height: 72, borderRadius: 16, background: 'var(--accent-soft)', color: 'var(--accent)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0, }}>
@ setUsername(e.target.value.replace(/^@/, ''))} style={{ flex: 1, background: 'transparent', border: 0, outline: 0, color: 'var(--text)', fontSize: 16, fontWeight: 600, lineHeight: 1, padding: 0, marginLeft: -2 }}/>
setAnon(v => !v)}>
Отправить анонимно
{sendErr && (
{sendErr}
)}
Внимательно проверь имя получателя
); } // Success screen shown after the gift is sent — the gift "flies off" into // Telegram with expanding rings, then a confetti + check celebration. The // sheet auto-dismisses (slides down) once the celebration has played. function SendSuccessSheet({ gifts, recipient }) { const closeAnim = React.useContext(SheetCloseContext); const [done, setDone] = React.useState(false); React.useEffect(() => { const t1 = setTimeout(() => setDone(true), 1000); const t2 = setTimeout(() => closeAnim(), 3000); return () => { clearTimeout(t1); clearTimeout(t2); }; }, []); const g = gifts[0] || {}; const n = gifts.length; const name = recipient?.anon ? 'анонимно' : ('@' + (recipient?.username || 'recipient')); const giftWord = n === 1 ? 'подарок' : n < 5 ? 'подарка' : 'подарков'; return (
{done && }
{!done && <>
} {!done ? (
{n > 1 && (
{n}
)}
) : (
)}
{done ? 'Отправлено! 🎉' : 'Отправляем…'}
{done ? ( <> {n > 1 ? `${n} ${giftWord}` : g.name} {n > 1 ? 'улетели' : 'улетел'} к{' '} {name} ) : ( 'Подарок летит в Telegram' )}
); } window.SendSelectSheet = SendSelectSheet; window.SendSuccessSheet = SendSuccessSheet; window.SendRecipientSheet = SendRecipientSheet;