import React, { useState, useEffect } from "react"; import { createRoot } from "react-dom/client"; import "./pregunta.css"; // ── Types & constants ───────────────────────────────────────────────────────── type CreditInfo = { token: string; username: string; expiresAt: number; tier: string; }; const STORAGE_KEY = "argumentes_credito"; const TIERS = [ { id: "dia", label: "1 día", price: "1€", days: 1 }, { id: "semana", label: "1 semana", price: "5€", days: 7 }, { id: "mes", label: "1 mes", price: "15€", days: 30 }, ] as const; type TierId = (typeof TIERS)[number]["id"]; // ── Helpers ─────────────────────────────────────────────────────────────────── function loadCredit(): CreditInfo | null { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const c = JSON.parse(raw) as CreditInfo; if (!c.token || !c.expiresAt || c.expiresAt < Date.now()) { localStorage.removeItem(STORAGE_KEY); return null; } return c; } catch { return null; } } function formatDate(ms: number): string { return new Date(ms).toLocaleDateString("es-ES", { day: "numeric", month: "long", year: "numeric", }); } function daysLeft(expiresAt: number): number { return Math.max(0, Math.ceil((expiresAt - Date.now()) / (1000 * 60 * 60 * 24))); } async function submitRedsysForm(data: { tpvUrl: string; merchantParams: string; signature: string; signatureVersion: string; }) { const form = document.createElement("form"); form.method = "POST"; form.action = data.tpvUrl; for (const [name, value] of Object.entries({ Ds_SignatureVersion: data.signatureVersion, Ds_MerchantParameters: data.merchantParams, Ds_Signature: data.signature, })) { const input = document.createElement("input"); input.type = "hidden"; input.name = name; input.value = value; form.appendChild(input); } document.body.appendChild(form); form.submit(); } // ── Main component ──────────────────────────────────────────────────────────── function App() { const params = new URLSearchParams(window.location.search); const creditOkOrder = params.get("credito_ok"); const isKo = params.get("ko") === "1"; // Credit state const [credit, setCredit] = useState(null); const [loaded, setLoaded] = useState(false); // Credit verification (polling after Redsys redirect) const [verifying, setVerifying] = useState(false); const [verifyError, setVerifyError] = useState(false); // Purchase flow const [selectedTier, setSelectedTier] = useState(null); const [username, setUsername] = useState(""); const [buying, setBuying] = useState(false); const [buyError, setBuyError] = useState(null); // Question submission const [text, setText] = useState(""); const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(null); const [sent, setSent] = useState(false); // Load credit from localStorage on mount useEffect(() => { setCredit(loadCredit()); setLoaded(true); }, []); // Poll for credit activation after Redsys redirect useEffect(() => { if (!creditOkOrder || !loaded || credit) return; setVerifying(true); let attempts = 0; const maxAttempts = 15; async function poll() { if (attempts >= maxAttempts) { setVerifying(false); setVerifyError(true); return; } attempts++; try { const res = await fetch( `/api/credito/estado?order=${encodeURIComponent(creditOkOrder!)}`, ); if (res.ok) { const data = (await res.json()) as { found: boolean; status?: string; token?: string; username?: string; expiresAt?: number; tier?: string; }; if (data.found && data.status === "active" && data.token && data.expiresAt) { const newCredit: CreditInfo = { token: data.token, username: data.username ?? "", expiresAt: data.expiresAt, tier: data.tier ?? "", }; localStorage.setItem(STORAGE_KEY, JSON.stringify(newCredit)); setCredit(newCredit); setVerifying(false); history.replaceState(null, "", "/pregunta"); return; } } } catch { // retry } setTimeout(poll, 2000); } poll(); }, [creditOkOrder, loaded, credit]); async function handleBuyCredit(e: React.FormEvent) { e.preventDefault(); if (!selectedTier) return; setBuyError(null); setBuying(true); try { const res = await fetch("/api/credito/iniciar", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tier: selectedTier, username: username.trim() }), }); if (!res.ok) throw new Error(await res.text()); await submitRedsysForm(await res.json()); } catch (err) { setBuyError(err instanceof Error ? err.message : "Error al procesar el pago"); setBuying(false); } } async function handleSubmitQuestion(e: React.FormEvent) { e.preventDefault(); if (!credit) return; setSubmitError(null); setSubmitting(true); try { const res = await fetch("/api/pregunta/enviar", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: text.trim(), token: credit.token }), }); if (!res.ok) { const msg = await res.text(); if (res.status === 401) { localStorage.removeItem(STORAGE_KEY); setCredit(null); throw new Error("Tu acceso ha expirado. Compra un nuevo plan."); } throw new Error(msg || `Error ${res.status}`); } setText(""); setSent(true); setTimeout(() => setSent(false), 4000); } catch (err) { setSubmitError(err instanceof Error ? err.message : "Error al enviar"); } finally { setSubmitting(false); } } // ── Loading ─────────────────────────────────────────────────────────────── if (!loaded) { return (
); } // ── Payment failed ──────────────────────────────────────────────────────── if (isKo) { return (
argument.es

Pago cancelado

El pago no se completó. Tu acceso no ha sido activado.

Intentar de nuevo
); } // ── Verifying payment ───────────────────────────────────────────────────── if (verifying || (creditOkOrder && !credit)) { return (
argument.es

Verificando tu pago…

{verifyError ? "No se pudo confirmar el pago. Si se completó, espera unos segundos y recarga." : "Esto puede tardar unos segundos."}

{verifyError ? ( Volver ) : (
)}
); } // ── Active credit — question form ───────────────────────────────────────── if (credit) { const days = daysLeft(credit.expiresAt); return (
argument.es
{days === 0 ? "Expira hoy" : `${days} día${days !== 1 ? "s" : ""} restante${days !== 1 ? "s" : ""}`}

Hola, {credit.username}

Acceso activo hasta el {formatDate(credit.expiresAt)}. Envía todas las preguntas que quieras.

{sent && (
✓ ¡Pregunta enviada! Se usará en el próximo sorteo.
)}