Files
argument.es/pregunta.tsx
Malin 2fac92356d feat: footer, auto-pause on no viewers, and Redsys question submissions
- Footer: add site-footer with one-liner + Cloud Host branding linking to cloudhost.es (mobile only, hidden on desktop)
- Auto-pause: game pauses automatically after AUTOPAUSE_DELAY_MS (default 60s) with no viewers connected; auto-resumes when first viewer connects; shows "Esperando espectadores…" in the UI
- Redsys: new /pregunta page lets viewers pay 1€ via Redsys to submit a fill-in question; submitted questions are used as prompts in the next round instead of AI generation; new redsys.ts implements HMAC_SHA256_V1 signing (3DES key derivation + HMAC-SHA256); notification endpoint at /api/redsys/notificacion handles server-to-server confirmation
- db.ts: questions table with createPendingQuestion / markQuestionPaid / getNextPendingQuestion / markQuestionUsed
- .env.sample: added AUTOPAUSE_DELAY_MS, PUBLIC_URL, REDSYS_* vars

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:03:30 +01:00

146 lines
4.5 KiB
TypeScript

import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import "./pregunta.css";
function App() {
const params = new URLSearchParams(window.location.search);
const isOk = params.get("ok") === "1";
const isKo = params.get("ko") === "1";
const [text, setText] = useState("");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
if (isOk) {
return (
<div className="pregunta">
<div className="pregunta__panel">
<a href="/" className="pregunta__logo">
<img src="/assets/logo.svg" alt="argument.es" />
</a>
<h1>¡Pregunta enviada!</h1>
<p className="pregunta__sub">
Tu pregunta se usará en el próximo sorteo entre las IAs. ¡Gracias por participar!
</p>
<a href="/" className="pregunta__btn">Volver al juego</a>
</div>
</div>
);
}
if (isKo) {
return (
<div className="pregunta">
<div className="pregunta__panel">
<a href="/" className="pregunta__logo">
<img src="/assets/logo.svg" alt="argument.es" />
</a>
<h1>Pago cancelado</h1>
<p className="pregunta__sub">
El pago no se completó. Tu pregunta no ha sido guardada.
</p>
<a href="/pregunta" className="pregunta__btn">Intentar de nuevo</a>
<div className="pregunta__links">
<a href="/">Volver al juego</a>
</div>
</div>
</div>
);
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
setSubmitting(true);
try {
const res = await fetch("/api/pregunta/iniciar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: text.trim() }),
});
if (!res.ok) {
const msg = await res.text();
throw new Error(msg || `Error ${res.status}`);
}
const data = (await res.json()) as {
tpvUrl: string;
merchantParams: string;
signature: string;
signatureVersion: string;
};
// Build and auto-submit the Redsys payment form
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();
} catch (err) {
setError(err instanceof Error ? err.message : "Error al procesar la solicitud");
setSubmitting(false);
}
}
return (
<div className="pregunta">
<div className="pregunta__panel">
<a href="/" className="pregunta__logo">
<img src="/assets/logo.svg" alt="argument.es" />
</a>
<h1>Propón una pregunta</h1>
<p className="pregunta__sub">
Paga 1 y tu pregunta de completar-la-frase se usará en el próximo sorteo entre las IAs.
</p>
<form onSubmit={handleSubmit} className="pregunta__form">
<label htmlFor="pregunta-text" className="pregunta__label">
Tu pregunta (frase de completar)
</label>
<textarea
id="pregunta-text"
className="pregunta__textarea"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder='Ejemplo: "Lo que más vergüenza da hacer en ___"'
maxLength={200}
required
rows={3}
autoFocus
/>
<div className="pregunta__hint">
{text.length}/200 caracteres · mínimo 10
</div>
{error && <div className="pregunta__error">{error}</div>}
<button
type="submit"
className="pregunta__submit"
disabled={submitting || text.trim().length < 10}
>
{submitting ? "Redirigiendo a pago…" : "Pagar 1€ y enviar"}
</button>
</form>
<div className="pregunta__links">
<a href="/">Volver al juego</a>
</div>
</div>
</div>
);
}
const root = createRoot(document.getElementById("root")!);
root.render(<App />);