// PF Order Form — two-column layout with real API const { useState: useOrderState, useEffect: useOrderEffect } = React; function parseAvitoUrls(text) { if (!text) return []; const normalized = text.replace(/(?<=\S)[\r\n]+(?=\S)/g, ''); const raw = normalized.match(/https?:\/\/(?:www\.)?avito\.ru\/\S+/g) || []; const seen = new Set(); return raw .map(u => u.replace(/["')\].,;]+$/, '').split('?')[0]) .filter(u => { if (seen.has(u)) return false; seen.add(u); return true; }); } function SliderField({ label, min, max, step, value, onChange, suffix = '', hint }) { return (
{value}{suffix}
onChange(Number(e.target.value))} /> { let v = Number(e.target.value); if (v < min) v = min; if (v > max) v = max; onChange(v); }} />
{min}{suffix}{max}{suffix}
{hint &&
{hint}
}
); } function OrderFormPage({ balance, onNavigate, onOrderPlaced }) { const [inputText, setInputText] = useOrderState(''); const [links, setLinks] = useOrderState([]); const [views, setViews] = useOrderState(30); // maps to fix_count in API const [days, setDays] = useOrderState(7); const [contacts, setContacts] = useOrderState(false); const [startDate, setStartDate] = useOrderState(() => { const d = new Date(); d.setDate(d.getDate() + 1); return d.toISOString().split('T')[0]; }); const [pricePerUnit, setPricePerUnit] = useOrderState(6); const [loading, setLoading] = useOrderState(false); const [error, setError] = useOrderState(''); const [submitted, setSubmitted] = useOrderState(false); const [submittedPrice, setSubmittedPrice] = useOrderState(0); useOrderEffect(() => { api.get('/api/orders/pf/price').then(data => { if (!data.__unauthorized) setPricePerUnit(data.price_per_unit || 6); }).catch(() => {}); }, []); const urlCount = links.length; const totalPrice = urlCount > 0 ? views * days * urlCount * pricePerUnit : 0; const handleInputChange = e => { const val = e.target.value; const parsed = parseAvitoUrls(val); const toAdd = parsed.filter(u => !links.includes(u)); if (toAdd.length) setLinks(prev => [...prev, ...toAdd]); setInputText(val); }; const removeLink = url => setLinks(prev => prev.filter(u => u !== url)); const handleSubmit = async () => { if (urlCount === 0) return setError('Вставьте хотя бы одну ссылку на объявление'); if (totalPrice > balance) return setError(`Недостаточно средств. Нужно ${totalPrice.toLocaleString('ru-RU')} ₽, на балансе ${balance.toLocaleString('ru-RU')} ₽`); setError(''); setLoading(true); try { await api.post('/api/orders/pf', { links, days, fix_count: views, contacts }); setSubmittedPrice(totalPrice); setSubmitted(true); onOrderPlaced && onOrderPlaced(totalPrice); setTimeout(() => onNavigate('cabinet'), 2200); } catch (e) { if (e.status === 402) setError(e.message || 'Недостаточно средств'); else setError(e.message || 'Ошибка создания заказа'); } finally { setLoading(false); } }; if (submitted) return (

Заказ принят!

Списано {submittedPrice.toLocaleString('ru-RU')} ₽

Возвращаем в кабинет...

); const noUrlsWarning = inputText.length > 5 && parseAvitoUrls(inputText).length === 0 && urlCount === 0; return (

Авито ПФ

Поведенческие факторы · {pricePerUnit} ₽ за просмотр
{error &&
{error}
}
{/* LEFT */}
Рекомендация
Начните с 15–30 просм./день без контактов в течение недели. После оживления органики постепенно добавляйте 5–8 контактов. Резкий рост контактов может временно снизить позиции.
{urlCount > 0 && ( ✓ {urlCount} {urlCount === 1 ? 'объявление' : urlCount < 5 ? 'объявления' : 'объявлений'} )}