// ROBOCASH chat message components
// Loaded after React + data.js
const { useState, useEffect, useRef, useMemo } = React;
const RC = window.ROBOCASH;
// ─── Brand tokens ───────────────────────────────────────────
const RCT = {
green: '#00C48F',
greenDeep: '#00A176',
greenSoft: '#D7F5E9',
greenHover: '#00B380',
ink: '#0E1F1B',
inkSoft: '#3A4A45',
mute: '#6B7975',
hint: '#94A6A0',
line: 'rgba(14,31,27,0.08)',
lineSoft: 'rgba(14,31,27,0.05)',
paper: '#FFFFFF',
cream: '#F7F4ED',
creamDeep: '#EEEADE',
navy: '#1B2A3A',
coral: '#FF7A59',
};
window.RCT = RCT;
// ─── Solomiya Avatar ────────────────────────────────────────
function SolomiyaAvatar({ size = 32, online = false, ring = false }) {
// Friendly cartoon-portrait placeholder: gradient circle + simple geometric face
return (
{/* simple geometric portrait */}
{online && (
)}
);
}
// ─── Wrappers ───────────────────────────────────────────────
function MsgRow({ side = 'agent', children, withAvatar = true }) {
if (side === 'user') {
return (
);
}
return (
{withAvatar && }
{children}
);
}
function AgentBubble({ children, first = false, last = true }) {
return (
{children}
);
}
function UserBubble({ children }) {
return (
{children}
);
}
// ─── Typing indicator ───────────────────────────────────────
function Typing() {
return (
{[0, 1, 2].map(i => (
))}
);
}
// ─── Partner strip (shown in welcome) ───────────────────────
function PartnerStrip() {
return (
{RC.PARTNERS.slice(0, 5).map((p, i) => (
{p.logoUrl ? (

{
const box = e.currentTarget.parentElement;
e.currentTarget.remove();
if (box) box.textContent = p.monogram;
}}
/>
) : p.monogram}
))}
+{RC.MFOS.length - 5} партнерів
);
}
// ─── Slider widget (amount / term) ──────────────────────────
function SliderWidget({ label, min, max, step, defaultValue, format, presets, onConfirm, confirmLabel = 'Далі', accent = RCT.green }) {
const [v, setV] = useState(defaultValue);
const pct = ((v - min) / (max - min)) * 100;
return (
{label}
{format(v)}
setV(Number(e.target.value))}
style={{
position: 'relative', width: '100%', height: 28,
WebkitAppearance: 'none', appearance: 'none',
background: 'transparent', margin: 0, padding: 0, cursor: 'pointer',
}}
className="rc-range"
/>
{format(min)}{format(max)}
{presets && (
{presets.map(p => (
))}
)}
);
}
// ─── Quick replies ──────────────────────────────────────────
function QuickReplies({ options, onPick, columns = 1 }) {
const hasPrimary = options.some(opt => opt.primary);
return (
{options.map(opt => {
const primary = !!opt.primary;
return (
);
})}
);
}
function normalizeLocalPhone(value) {
let digits = String(value || '').replace(/\D/g, '');
if (digits.startsWith('380')) digits = digits.slice(3);
else if (digits.startsWith('80')) digits = digits.slice(2);
else if (digits.startsWith('0')) digits = digits.slice(1);
return digits.slice(0, 9);
}
function formatUAphone(localDigits) {
const d = normalizeLocalPhone(localDigits);
let s = '+380 ';
if (d.length > 0) s += d.slice(0, 2);
if (d.length > 2) s += ' ' + d.slice(2, 5);
if (d.length > 5) s += ' ' + d.slice(5, 7);
if (d.length > 7) s += ' ' + d.slice(7, 9);
return s;
}
// ─── Phone / ИНН input widget ───────────────────────────────
function PhoneWidget({ onConfirm }) {
const [phone, setPhone] = useState('');
const [consent, setConsent] = useState(true);
const formatted = useMemo(() => formatUAphone(phone), [phone]);
const valid = phone.length === 9;
return (
Номер телефону
Шифрування TLS 1.3 • Не передаємо третім особам
);
}
// ─── Processing / progress bar ──────────────────────────────
function ProcessingWidget({ onDone }) {
const [stage, setStage] = useState(0);
const stages = [
'Аналізую ваш профіль…',
'Перевіряю партнерські МФО…',
'Розраховую % одобрення…',
'Формую список пропозицій…',
];
useEffect(() => {
const t1 = setTimeout(() => setStage(1), 700);
const t2 = setTimeout(() => setStage(2), 1500);
const t3 = setTimeout(() => setStage(3), 2300);
const t4 = setTimeout(() => onDone && onDone(), 3300);
return () => [t1, t2, t3, t4].forEach(clearTimeout);
}, []);
const pct = ((stage + 1) / stages.length) * 100;
return (
Підбираю кредити
{stages[stage]}
);
}
// ─── MFO card ───────────────────────────────────────────────
function MFOCard({ mfo, requestedAmount, isTop = false }) {
return (
{isTop && (
★ Рекомендуємо
)}
{mfo.logoUrl ? (

{
const box = e.currentTarget.parentElement;
e.currentTarget.remove();
if (box) box.textContent = mfo.monogram;
}}
/>
) : mfo.monogram}
Шанс одобрення
{mfo.approval}%
{mfo.perks.slice(0, 3).map(p => (
))}
);
}
function Stat({ label, value }) {
return (
);
}
// ─── MFO vertical list ─────────────────────────────────────
function MFOCarousel({ mfos, requestedAmount }) {
return (
{mfos.map((m, i) => (
))}
);
}
// ─── Summary chip (collected user answer summary) ───────────
function SummaryChip({ icon, label, value }) {
return (
{label}:
{value}
);
}
Object.assign(window, {
SolomiyaAvatar, MsgRow, AgentBubble, UserBubble, Typing,
PartnerStrip, SliderWidget, QuickReplies, PhoneWidget,
ProcessingWidget, MFOCard, MFOCarousel, SummaryChip,
});