Fase 3 (isla): renombrar hermandad en React/TSX

rename_guild_view ya devolvía JSON (session_id de Stripe). GuildRenameForm.tsx:
select de hermandades (donde el usuario es GM) + nuevo nombre/confirmar + contraseña
+ token -> Stripe Checkout. La vista pasa guild_names por json_script; se conserva
la rama {% if no_guild_leader_message %} en Django. Se elimina el JS jQuery muerto.

Verificado: check OK, render de ambas ramas correcto, redirige a login sin sesión.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 21:45:48 +00:00
parent 6be04e4e37
commit a4da7f068b
5 changed files with 138 additions and 37 deletions
+114
View File
@@ -0,0 +1,114 @@
import { useState } from 'react'
import { redirectToStripeCheckout } from '../stripe'
interface Props {
url: string
csrfToken: string
guilds: string[]
}
export function GuildRenameForm({ url, csrfToken, guilds }: Props) {
const [oldName, setOldName] = useState('')
const [newName, setNewName] = useState('')
const [confName, setConfName] = useState('')
const [password, setPassword] = 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('old-guild-name', oldName)
body.set('new-guild-name', newName.trim())
body.set('conf-new-guild-name', confName.trim())
body.set('cur-password', password.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 (
<>
<form id="nw-rename-guild-form" onSubmit={handleSubmit} acceptCharset="utf-8">
<table className="middle-center-table">
<tbody>
<tr>
<td>
<select name="old-guild-name" value={oldName} onChange={(e) => setOldName(e.target.value)} required>
<option value="">Selecciona tu hermandad</option>
{guilds.map((g) => (
<option value={g} key={g}>{g}</option>
))}
</select>
</td>
</tr>
<tr>
<td>
<input type="text" maxLength={24} name="new-guild-name" placeholder="Nuevo nombre de hermandad"
value={newName} onChange={(e) => setNewName(e.target.value)} required />
</td>
</tr>
<tr>
<td>
<input type="text" maxLength={24} name="conf-new-guild-name" placeholder="Confirmar nuevo nombre"
value={confName} onChange={(e) => setConfName(e.target.value)} required />
</td>
</tr>
<tr>
<td>
<input type="password" name="cur-password" placeholder="Contraseña actual"
value={password} onChange={(e) => setPassword(e.target.value)} required />
</td>
</tr>
<tr>
<td>
<input type="text" name="security-token" placeholder="Token de seguridad"
value={token} onChange={(e) => setToken(e.target.value)} required />
</td>
</tr>
<tr>
<td>
<button type="submit" className="rename-guild-button" disabled={busy}>
{busy ? 'Procesando…' : 'Proceder al Pago'}
</button>
</td>
</tr>
</tbody>
</table>
</form>
<hr />
<div className="alert-message" id="rename-guild-response" style={{ display: message ? 'block' : 'none' }}>
{message && <span dangerouslySetInnerHTML={{ __html: message.html }} />}
</div>
</>
)
}