From 08c36619f0c1e3cdd344c9a0384b64f4d1aa4f30 Mon Sep 17 00:00:00 2001 From: adevopg Date: Sun, 12 Jul 2026 21:57:49 +0000 Subject: [PATCH] Fase 3 (isla): recuperar personaje (restore_character) en React/TSX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../src/components/RestoreCharacterForm.tsx | 80 +++++++++++++++++++ frontend/src/entries/restore_character.tsx | 14 ++++ frontend/vite.config.ts | 1 + .../templates/partials/restore_character.html | 16 ++-- home/views/characters.py | 26 +++--- 5 files changed, 116 insertions(+), 21 deletions(-) create mode 100644 frontend/src/components/RestoreCharacterForm.tsx create mode 100644 frontend/src/entries/restore_character.tsx diff --git a/frontend/src/components/RestoreCharacterForm.tsx b/frontend/src/components/RestoreCharacterForm.tsx new file mode 100644 index 0000000..e49b1f6 --- /dev/null +++ b/frontend/src/components/RestoreCharacterForm.tsx @@ -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: 'Error en el servidor. Inténtalo más tarde.' }) + setBusy(false) + } + } + + return ( + <> +
+ +
+ +
+
+ {message && } +
+ + ) +} diff --git a/frontend/src/entries/restore_character.tsx b/frontend/src/entries/restore_character.tsx new file mode 100644 index 0000000..8ebeed4 --- /dev/null +++ b/frontend/src/entries/restore_character.tsx @@ -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( + + + , + ) +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 6f71133..4a2a99b 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -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'), }, }, }, diff --git a/home/templates/partials/restore_character.html b/home/templates/partials/restore_character.html index 3344305..73d3ba7 100644 --- a/home/templates/partials/restore_character.html +++ b/home/templates/partials/restore_character.html @@ -33,15 +33,13 @@

{{ message }}

{% endif %} {% if character_list %} -
- {% csrf_token %} - - -
+ {% load django_vite %} + +
+ {{ character_data|json_script:"restore-character-data" }} + {% vite_asset 'src/entries/restore_character.tsx' %} {% else %} No hay personajes eliminados disponibles para restaurar. {% endif %} diff --git a/home/views/characters.py b/home/views/characters.py index b22365f..9715eca 100644 --- a/home/views/characters.py +++ b/home/views/characters.py @@ -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': 'No se seleccionó un personaje para restaurar.'}) - # 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': 'Personaje restaurado exitosamente.', + 'redirect': reverse('my-account'), + }) else: - messages.error(request, "Hubo un error al restaurar el personaje.") - return redirect('restore-character') - + return JsonResponse({'success': False, 'message': 'Hubo un error al restaurar el personaje.'}) 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'Hubo un error al intentar restaurar el personaje: {str(e)}'}) # 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')