Files
NightSpire/frontend/src/components/TransferForm.tsx
T
Inna 6be04e4e37 Fase 3: migra gold, transfer, revive y unstuck a islas
- revive/unstuck (acciones SOAP gratuitas): reutilizan CharacterPurchaseForm; se
  hace el precio opcional (botón sin "por X €" cuando no hay precio). El componente
  ya maneja la respuesta sin session_id mostrando el mensaje.
- gold: variante GoldForm (personaje + cantidad de oro -> Stripe). La vista pasa
  ahora character_names y gold_options (serializables) por json_script.
- transfer: variante TransferForm (personaje + cuenta destino + token -> Stripe).
  La vista pasa character_names por json_script.

Verificado: check OK, plantillas renderizan las islas, las 4 páginas redirigen a
login sin sesión.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:42:35 +00:00

97 lines
3.2 KiB
TypeScript

import { useState } from 'react'
import { redirectToStripeCheckout } from '../stripe'
interface Props {
url: string
csrfToken: string
characters: string[]
price: string
}
export function TransferForm({ url, csrfToken, characters, price }: Props) {
const [character, setCharacter] = useState('')
const [destination, setDestination] = useState('')
const [token, setToken] = useState('')
const [busy, setBusy] = useState(false)
const [message, setMessage] = useState<{ ok: boolean; html: string } | null>(null)
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (busy) return
setBusy(true)
setMessage(null)
const body = new URLSearchParams()
body.set('character', character)
body.set('destination_account', destination.trim())
body.set('security_token', token.trim())
body.set('csrfmiddlewaretoken', csrfToken)
try {
const resp = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': csrfToken,
Accept: 'application/json',
},
credentials: 'same-origin',
body: body.toString(),
})
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const data: { success?: boolean; message?: string; session_id?: string; stripe_public_key?: string } =
await resp.json()
if (data.success && data.session_id && data.stripe_public_key) {
await redirectToStripeCheckout(data.stripe_public_key, data.session_id)
return
}
setMessage({ ok: !!data.success, html: data.message || '' })
setBusy(false)
} catch {
setMessage({ ok: false, html: '<span class="red-form-response">Error al iniciar el pago. Inténtalo más tarde.</span>' })
setBusy(false)
}
}
return (
<div className="centered">
<form onSubmit={handleSubmit} acceptCharset="utf-8">
<select name="character" value={character} onChange={(e) => setCharacter(e.target.value)} required>
<option value="" disabled>Seleccione un personaje</option>
{characters.map((c) => (
<option value={c} key={c}>{c}</option>
))}
</select>
<br />
<input
type="text"
name="destination_account"
placeholder="Cuenta destino"
required
value={destination}
onChange={(e) => setDestination(e.target.value)}
/>
<br />
<input
type="password"
name="security_token"
placeholder="Token de seguridad"
required
value={token}
onChange={(e) => setToken(e.target.value)}
/>
<br />
<button type="submit" className="transfer-button" disabled={busy || !character || !destination || !token}>
{busy ? 'Procesando…' : `Transferir por ${price} €`}
</button>
</form>
<hr />
<center>
<div id="transfer-character-response" className="alert-message" style={{ display: message ? 'block' : 'none' }}>
{message && <span dangerouslySetInnerHTML={{ __html: message.html }} />}
</div>
</center>
</div>
)
}