// Panel admina: promowanie ofert - plany, pakiety, konfiguracja AutoPay i historia platnosci. import { useCallback, useEffect, useState } from 'react'; import { apiFetch } from '../auth'; type IconComponent = (props: { name: string }) => JSX.Element | null; type Plan = { id: number; name: string; durationDays: number; price: number; active: boolean; position: number }; type Pack = { id: number; name: string; quantity: number; price: number; active: boolean; position: number }; type Order = { id: number; userEmail: string; kind: string; itemName: string; amount: number; method: string; listingId: number | null; status: string; createdAt: string; paidAt: string | null; }; type PayCfg = { serviceId: string | null; secretKeySet: boolean; hashSeparator: string; sandbox: boolean; enabled: boolean }; const zl = (n: number) => `${new Intl.NumberFormat('pl-PL', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(n)} zł`; const em = (e: unknown) => (e instanceof Error ? e.message : 'Wystąpił błąd.'); const dt = (iso: string | null) => (iso ? new Date(iso).toLocaleString('pl-PL') : '-'); const emptyPlan = () => ({ id: null as number | null, name: '', durationDays: 14, price: '', active: true, position: 0 }); const emptyPack = () => ({ id: null as number | null, name: '', quantity: 5, price: '', active: true, position: 0 }); export function AdminPromotionView({ Icon }: { Icon: IconComponent }) { const [plans, setPlans] = useState([]); const [packs, setPacks] = useState([]); const [orders, setOrders] = useState([]); const [error, setError] = useState(null); const [note, setNote] = useState(null); const [planForm, setPlanForm] = useState(emptyPlan()); const [packForm, setPackForm] = useState(emptyPack()); const [pay, setPay] = useState({ serviceId: '', secretKey: '', hashSeparator: '|', sandbox: true, enabled: false }); const [secretSet, setSecretSet] = useState(false); const load = useCallback(async () => { try { const [pl, pk, ord, cfg] = await Promise.all([ apiFetch('/admin/promotion/plans'), apiFetch('/admin/promotion/packages'), apiFetch('/admin/promotion/orders'), apiFetch('/admin/payment-config'), ]); setPlans(pl); setPacks(pk); setOrders(ord); setSecretSet(cfg.secretKeySet); setPay((f) => ({ ...f, serviceId: cfg.serviceId ?? '', hashSeparator: cfg.hashSeparator || '|', sandbox: cfg.sandbox, enabled: cfg.enabled })); } catch (e) { setError(em(e)); } }, []); useEffect(() => { load(); }, [load]); const flash = (msg: string) => { setNote(msg); setError(null); }; const savePlan = async () => { try { const body = JSON.stringify({ name: planForm.name, durationDays: Number(planForm.durationDays), price: Number(planForm.price), active: planForm.active, position: Number(planForm.position), }); if (planForm.id) { await apiFetch(`/admin/promotion/plans/${planForm.id}`, { method: 'PUT', body }); } else { await apiFetch('/admin/promotion/plans', { method: 'POST', body }); } setPlanForm(emptyPlan()); flash('Zapisano plan.'); await load(); } catch (e) { setError(em(e)); } }; const savePack = async () => { try { const body = JSON.stringify({ name: packForm.name, quantity: Number(packForm.quantity), price: Number(packForm.price), active: packForm.active, position: Number(packForm.position), }); if (packForm.id) { await apiFetch(`/admin/promotion/packages/${packForm.id}`, { method: 'PUT', body }); } else { await apiFetch('/admin/promotion/packages', { method: 'POST', body }); } setPackForm(emptyPack()); flash('Zapisano pakiet.'); await load(); } catch (e) { setError(em(e)); } }; const savePay = async () => { try { const body = JSON.stringify({ serviceId: pay.serviceId || null, secretKey: pay.secretKey || null, hashSeparator: pay.hashSeparator, sandbox: pay.sandbox, enabled: pay.enabled, }); const cfg = await apiFetch('/admin/payment-config', { method: 'PUT', body }); setSecretSet(cfg.secretKeySet); setPay((f) => ({ ...f, secretKey: '' })); flash('Zapisano konfigurację AutoPay.'); } catch (e) { setError(em(e)); } }; const toggle = async (kind: 'plans' | 'packages', id: number) => { try { await apiFetch(`/admin/promotion/${kind}/${id}/toggle`, { method: 'PATCH' }); await load(); } catch (e) { setError(em(e)); } }; const remove = async (kind: 'plans' | 'packages', id: number, label: string) => { if (!window.confirm(`Usunąć „${label}"?`)) { return; } try { await apiFetch(`/admin/promotion/${kind}/${id}`, { method: 'DELETE' }); await load(); } catch (e) { setError(em(e)); } }; return (
{(error || note) && (error ?

{error}

:

{note}

)} {/* Plany promowania */}

{planForm.id ? 'Edytuj plan promowania' : 'Plany promowania'}

{planForm.id && }
{plans.map((p) => (
{p.name} {p.durationDays} dni · {zl(p.price)}
{p.active ? 'Aktywny' : 'Nieaktywny'}
))} {plans.length === 0 &&

Brak planów.

}
{/* Pakiety promowania */}

{packForm.id ? 'Edytuj pakiet' : 'Pakiety promowań'}

{packForm.id && }
{packs.map((p) => (
{p.name} {p.quantity} szt. · {zl(p.price)} ({zl(p.price / p.quantity)} / szt.)
{p.active ? 'Aktywny' : 'Nieaktywny'}
))} {packs.length === 0 &&

Brak pakietów.

}
{/* Konfiguracja AutoPay */}

Bramka szybkich płatności - AutoPay

Bez danych dostępowych i włączonej bramki zakup promowania jest niedostępny. Adres ITN w panelu AutoPay (Ustawienia serwisu → Konfiguracja techniczna → Edytuj): {window.location.origin}/api/payments/autopay/itn

{/* Historia platnosci */}

Historia płatności

{orders.length}
{orders.length === 0 ? (

Brak płatności.

) : (
{orders.map((o) => ( ))}
DataUżytkownikPozycjaKwotaMetodaStatus
{dt(o.createdAt)} {o.userEmail} {o.itemName} {zl(o.amount)} {o.method} {o.status === 'PAID' ? 'Opłacone' : o.status === 'PENDING' ? 'Oczekuje' : 'Błąd'}
)}
); }