'use client'; import { useState, useCallback } from 'react'; import { QRScanner } from '@/components/QRScanner'; import { Button, Card, Input } from '@/components/ui'; import { api } from '@/lib/api'; import { formatPoints } from '@/lib/utils'; import { useAuthStore } from '@/lib/store'; import type { SpendResult } from '@/lib/types'; type Step = 'scan' | 'form' | 'result'; // Default cap: partner can spend at most 50% of the purchase total in points. // The backend validates against the actual partner's max_spend_pct. const DEFAULT_MAX_SPEND_PCT = 0.5; export default function ScanPage() { const { user, logout } = useAuthStore(); const [step, setStep] = useState('scan'); const [qrToken, setQrToken] = useState(''); const [purchaseTotal, setPurchaseTotal] = useState(''); const [pointsToSpend, setPointsToSpend] = useState(''); const [result, setResult] = useState(null); const [errorMsg, setErrorMsg] = useState(''); const [loading, setLoading] = useState(false); const handleScan = useCallback((token: string) => { setQrToken(token); setStep('form'); }, []); function handlePurchaseTotalChange(val: string) { setPurchaseTotal(val); const total = parseFloat(val); if (!isNaN(total) && total > 0) { setPointsToSpend(String(Math.floor(total * DEFAULT_MAX_SPEND_PCT))); } else { setPointsToSpend(''); } } function handlePointsChange(val: string) { const total = parseFloat(purchaseTotal); const max = Math.floor(total * DEFAULT_MAX_SPEND_PCT); const entered = parseInt(val, 10); // Prevent partner from setting points above the allowed cap. if (!isNaN(entered) && !isNaN(max) && entered > max) { setPointsToSpend(String(max)); } else { setPointsToSpend(val); } } const totalNum = parseFloat(purchaseTotal) || 0; const pointsNum = parseInt(pointsToSpend, 10) || 0; const remainder = Math.max(0, totalNum - pointsNum); async function handleSubmit() { if (pointsNum <= 0) return; setLoading(true); setErrorMsg(''); try { const data = await api.post('/api/v1/partner/spend', { qr_token: qrToken, amount: pointsNum, }); setResult(data); setStep('result'); } catch (e) { const msg = e instanceof Error ? e.message : 'Ошибка'; if (msg.includes('insufficient')) { setErrorMsg('Недостаточно поинтов на балансе студента.'); } else if (msg.includes('already been used')) { setErrorMsg('Этот QR-код уже был использован. Попросите студента создать новый.'); } else if (msg.includes('invalid or expired') || msg.includes('expired')) { setErrorMsg('QR-код недействителен или истёк. Попросите студента создать новый QR.'); } else { setErrorMsg(msg); } setStep('result'); } finally { setLoading(false); } } function reset() { setStep('scan'); setQrToken(''); setPurchaseTotal(''); setPointsToSpend(''); setResult(null); setErrorMsg(''); } return (
{/* Partner header */}
CU Points — {user?.name ?? 'Партнёр'}

Оплата поинтами

{/* ── Step 1: QR scan ─────────────────────────────── */} {step === 'scan' && (

Наведи камеру на QR студента

)} {/* ── Step 2: Amount entry ─────────────────────────── */} {step === 'form' && (
✓

Студент отсканирован

handlePurchaseTotalChange(e.target.value)} placeholder="0" autoFocus /> handlePointsChange(e.target.value)} placeholder="0" /> {totalNum > 0 && (

Остаток к оплате:{' '} {remainder.toFixed(0)} ₽

)}
)} {/* ── Step 3: Result ───────────────────────────────── */} {step === 'result' && ( {result ? ( <>

✓

Списано {result.spent} поинтов

Новый баланс студента:{' '} {formatPoints(result.new_balance)}

) : ( <>

✗

{errorMsg || 'Неизвестная ошибка'}

)}
)}
); }