Fase 3 (isla): recuperar personaje (restore_character) en React/TSX
restore_character_view pasa de redirect+messages a JSON en POST: guid -> SOAP
.char del restore -> {success, message, redirect}. RestoreCharacterForm.tsx
(select de personajes borrados + botón) hace fetch y en éxito redirige a my-account.
La vista pasa character_data por json_script; se conserva la rama sin personajes.
Verificado: check OK, ambas ramas renderizan, redirige a login sin sesión.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
interface DeletedChar {
|
||||
guid: number
|
||||
name: string
|
||||
level: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
url: string
|
||||
csrfToken: string
|
||||
characters: DeletedChar[]
|
||||
}
|
||||
|
||||
export function RestoreCharacterForm({ url, csrfToken, characters }: Props) {
|
||||
const [guid, setGuid] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [done, setDone] = useState(false)
|
||||
const [message, setMessage] = useState<{ ok: boolean; html: string } | null>(null)
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
if (busy || done || !guid) return
|
||||
setBusy(true)
|
||||
setMessage(null)
|
||||
|
||||
const body = new URLSearchParams()
|
||||
body.set('guid', guid)
|
||||
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; redirect?: string } = await resp.json()
|
||||
setMessage({ ok: !!data.success, html: data.message || '' })
|
||||
if (data.success && data.redirect) {
|
||||
setDone(true)
|
||||
setTimeout(() => {
|
||||
window.location.href = data.redirect as string
|
||||
}, 2000)
|
||||
} else {
|
||||
setBusy(false)
|
||||
}
|
||||
} catch {
|
||||
setMessage({ ok: false, html: '<span class="red-form-response">Error en el servidor. Inténtalo más tarde.</span>' })
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={handleSubmit} acceptCharset="utf-8">
|
||||
<select name="guid" value={guid} onChange={(e) => setGuid(e.target.value)} required>
|
||||
<option value="" disabled>Selecciona un personaje</option>
|
||||
{characters.map((c) => (
|
||||
<option value={c.guid} key={c.guid}>
|
||||
{c.name} (Nivel {c.level})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<br />
|
||||
<button type="submit" disabled={busy || done || !guid}>
|
||||
{done ? 'Hecho' : busy ? 'Restaurando…' : 'Restaurar Personaje'}
|
||||
</button>
|
||||
</form>
|
||||
<div className="alert-message" style={{ display: message ? 'block' : 'none' }}>
|
||||
{message && <span dangerouslySetInnerHTML={{ __html: message.html }} />}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { RestoreCharacterForm } from '../components/RestoreCharacterForm'
|
||||
|
||||
const el = document.getElementById('restore-character-app')
|
||||
if (el) {
|
||||
const dEl = document.getElementById('restore-character-data')
|
||||
const characters = dEl ? JSON.parse(dEl.textContent || '[]') : []
|
||||
createRoot(el).render(
|
||||
<StrictMode>
|
||||
<RestoreCharacterForm url={el.dataset.url || ''} csrfToken={el.dataset.csrf || ''} characters={characters} />
|
||||
</StrictMode>,
|
||||
)
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export default defineConfig({
|
||||
rename_guild: resolve(__dirname, 'src/entries/rename_guild.tsx'),
|
||||
vote_points: resolve(__dirname, 'src/entries/vote_points.tsx'),
|
||||
history_table: resolve(__dirname, 'src/entries/history_table.tsx'),
|
||||
restore_character: resolve(__dirname, 'src/entries/restore_character.tsx'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -33,15 +33,13 @@
|
||||
<p>{{ message }}</p>
|
||||
{% endif %}
|
||||
{% if character_list %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<select name="guid">
|
||||
{% for character in character_list %}
|
||||
<option value="{{ character.guid }}">{{ character.name }} (LvL {{ character.level }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit">Restaurar Personaje</button>
|
||||
</form>
|
||||
{% load django_vite %}
|
||||
<!-- Isla React (Vite): recuperar personaje borrado (guid -> SOAP). -->
|
||||
<div id="restore-character-app"
|
||||
data-csrf="{{ csrf_token }}"
|
||||
data-url="{% url 'restore-character' %}"></div>
|
||||
{{ character_data|json_script:"restore-character-data" }}
|
||||
{% vite_asset 'src/entries/restore_character.tsx' %}
|
||||
{% else %}
|
||||
<span>No hay personajes eliminados disponibles para restaurar.</span>
|
||||
{% endif %}
|
||||
|
||||
+14
-12
@@ -121,25 +121,23 @@ def restore_character_view(request):
|
||||
guid = request.POST.get('guid')
|
||||
|
||||
if not guid:
|
||||
messages.error(request, "No se seleccionó un personaje para restaurar.")
|
||||
return redirect('restore-character')
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No se seleccionó un personaje para restaurar.</span>'})
|
||||
|
||||
# Aquí es donde usamos SOAP en lugar de la actualización directa
|
||||
# Restaurar vía SOAP y responder en JSON (isla React)
|
||||
try:
|
||||
command = f".char del restore {guid}" # Comando SOAP para restaurar el personaje
|
||||
response = execute_soap_command(command) # Ejecuta el comando SOAP
|
||||
|
||||
# Si la respuesta es exitosa, informamos al usuario
|
||||
if response == "success":
|
||||
messages.success(request, "Personaje restaurado exitosamente.")
|
||||
return redirect('my-account') # Redirigir a la cuenta del usuario
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': '<span class="ok-form-response">Personaje restaurado exitosamente.</span>',
|
||||
'redirect': reverse('my-account'),
|
||||
})
|
||||
else:
|
||||
messages.error(request, "Hubo un error al restaurar el personaje.")
|
||||
return redirect('restore-character')
|
||||
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Hubo un error al restaurar el personaje.</span>'})
|
||||
except Exception as e:
|
||||
messages.error(request, f"Hubo un error al intentar restaurar el personaje: {str(e)}")
|
||||
return redirect('restore-character')
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Hubo un error al intentar restaurar el personaje: {str(e)}</span>'})
|
||||
|
||||
# Realizar la consulta para obtener los personajes eliminados de la cuenta
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
@@ -172,7 +170,11 @@ def restore_character_view(request):
|
||||
|
||||
# Renderiza la plantilla con los personajes eliminados
|
||||
return render(request, 'account/restore_character.html', {
|
||||
'character_list': character_list
|
||||
'character_list': character_list,
|
||||
'character_data': [
|
||||
{'guid': c['guid'], 'name': c['name'], 'level': c['level']}
|
||||
for c in character_list
|
||||
],
|
||||
})
|
||||
def restore_items_view(request):
|
||||
username = request.session.get('username')
|
||||
|
||||
Reference in New Issue
Block a user