From b2069df4c3c6ab909ada4d3b0aca4771bce78393 Mon Sep 17 00:00:00 2001 From: adevopg Date: Sun, 12 Jul 2026 21:23:17 +0000 Subject: [PATCH] =?UTF-8?q?Fase=203=20(isla):=20dashboard=20de=20=C2=ABMi?= =?UTF-8?q?=20cuenta=C2=BB=20en=20React/TSX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De las ~508 líneas de partials/my-account.html, solo la parte superior es dinámica (datos de cuenta, estado, puntos, token, personajes); el resto son paneles de navegación estáticos (enlaces), que se quedan en Django. - API: account_me_api -> /api/account/me/ (protegido por sesión, 401 si no hay): user_info, account_status, dp/vp, créditos Battlepay, token de seguridad y personajes. Reutiliza get_account_characters/get_account_status/formatear_fecha. Resiliente si acore no está disponible. - React: AccountDashboard.tsx (fieldsets de datos + estado con contador de baneo en vivo + rejilla de personajes). Entry my_account. - partials/my-account.html: los 2 fieldsets + la rejilla de personajes + el script del contador se sustituyen por #account-dashboard-app; los paneles de utilidades e historiales siguen en Django. Verificado: check OK, /api/account/me/ 401 sin sesión, my-account redirige a login. (El render con datos reales requiere sesión + BD AzerothCore.) Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/components/AccountDashboard.tsx | 176 +++++++++++++++++++ frontend/src/entries/my_account.tsx | 12 ++ frontend/vite.config.ts | 1 + home/api_urls.py | 1 + home/templates/partials/my-account.html | 106 +---------- home/views/api.py | 47 +++++ 6 files changed, 241 insertions(+), 102 deletions(-) create mode 100644 frontend/src/components/AccountDashboard.tsx create mode 100644 frontend/src/entries/my_account.tsx diff --git a/frontend/src/components/AccountDashboard.tsx b/frontend/src/components/AccountDashboard.tsx new file mode 100644 index 0000000..fd4d157 --- /dev/null +++ b/frontend/src/components/AccountDashboard.tsx @@ -0,0 +1,176 @@ +import { useEffect, useState } from 'react' + +interface UserInfo { + bnet_email: string + username: string + reg_mail: string + email: string + last_ip: string + last_attempt_ip: string + joindate: string +} + +interface AccountStatus { + is_banned: boolean + unban_date?: string + remaining_time?: number + is_recruited?: boolean + recruited_count?: number +} + +interface Character { + name: string + level: number + gold: number + silver: number + copper: number + zone: string + class_css: string + image_url: string +} + +interface AccountData { + authenticated: boolean + error?: string + user_info: UserInfo + account_status: AccountStatus + dp: number + vp: number + battlepay_credits: number + token_status: string + token_date: string | null + characters: Character[] +} + +function fmtRemaining(secs: number): string { + if (secs <= 0) return '0h 0m 0s' + const h = Math.floor(secs / 3600) + const m = Math.floor((secs % 3600) / 60) + const s = secs % 60 + return `${h}h ${m}m ${s}s` +} + +export function AccountDashboard() { + const [data, setData] = useState(null) + const [error, setError] = useState(null) + const [remaining, setRemaining] = useState(null) + + useEffect(() => { + let cancelled = false + fetch('/api/account/me/', { headers: { Accept: 'application/json' }, credentials: 'same-origin' }) + .then((r) => r.json()) + .then((d: AccountData) => { + if (cancelled) return + if (d.error) setError(d.error) + else { + setData(d) + if (d.account_status?.is_banned && d.account_status.remaining_time) { + setRemaining(d.account_status.remaining_time) + } + } + }) + .catch(() => !cancelled && setError('No se pudieron cargar los datos de la cuenta.')) + return () => { + cancelled = true + } + }, []) + + useEffect(() => { + if (remaining === null) return + if (remaining <= 0) { + window.location.reload() + return + } + const id = setInterval(() => setRemaining((r) => (r === null ? null : r - 1)), 1000) + return () => clearInterval(id) + }, [remaining]) + + if (error) return

{error}

+ if (!data) return

Cargando datos de la cuenta…

+ + const { user_info: u, account_status: st } = data + + return ( + <> +
+ Datos básicos +
+

Cuenta (Battle.net): {u.bnet_email}

+

Cuenta de juego: {u.username}

+

Correo de registro: {u.reg_mail}

+

Correo actual: {u.email}

+

Fecha de registro: {u.joindate}

+

Última IP (web): {u.last_ip}

+

Última IP (Servidor): {u.last_attempt_ip}

+
+
+ +
+ Estado de la cuenta +
+ Nivel 1 +

PD: {data.dp}

+

PV: {data.vp}

+

Créditos Battlepay: {data.battlepay_credits}

+

+ Token de Seguridad:{' '} + + {data.token_status === 'Solicitado' ? `${data.token_status} - ${data.token_date}` : data.token_status} + +

+ + {st?.is_banned ? ( + <> +

Cuenta baneada:

+

Fin de la suspensión: {st.unban_date}

+

+ Tiempo restante: {remaining !== null ? fmtRemaining(remaining) : ''} +

+ + ) : ( + <> +

Cuenta baneada: No (Consultar historial)

+

Cuenta reclutada: {st?.is_recruited ? 'Sí' : 'No'}

+

Amigos reclutados: {st?.recruited_count ?? 0}

+ + )} +
+
+ + {data.characters.length > 0 && ( +
+
+

Mis personajes

+
+
+ {data.characters.map((c) => ( +
+
+ {c.name} +

{c.name}

+

Nivel {c.level}

+

{c.zone}

+

+ + {c.gold} + {' '} + {c.silver} + {' '} + {c.copper} + + +

+
+
+ ))} +
+
+ )} + + ) +} diff --git a/frontend/src/entries/my_account.tsx b/frontend/src/entries/my_account.tsx new file mode 100644 index 0000000..def06da --- /dev/null +++ b/frontend/src/entries/my_account.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { AccountDashboard } from '../components/AccountDashboard' + +const el = document.getElementById('account-dashboard-app') +if (el) { + createRoot(el).render( + + + , + ) +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 273e7e5..2ac2272 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ select_account: resolve(__dirname, 'src/entries/select_account.tsx'), recover: resolve(__dirname, 'src/entries/recover.tsx'), reset_password: resolve(__dirname, 'src/entries/reset_password.tsx'), + my_account: resolve(__dirname, 'src/entries/my_account.tsx'), }, }, }, diff --git a/home/api_urls.py b/home/api_urls.py index 82176b4..ab26867 100644 --- a/home/api_urls.py +++ b/home/api_urls.py @@ -6,4 +6,5 @@ urlpatterns = [ path('home/news/', views.home_news_api, name='api_home_news'), path('home/status/', views.home_status_api, name='api_home_status'), path('account/select/', views.account_select_api, name='api_account_select'), + path('account/me/', views.account_me_api, name='api_account_me'), ] diff --git a/home/templates/partials/my-account.html b/home/templates/partials/my-account.html index ac8acef..e01d628 100644 --- a/home/templates/partials/my-account.html +++ b/home/templates/partials/my-account.html @@ -1,3 +1,4 @@ +{% load django_vite %}
@@ -6,89 +7,11 @@

Información

- - +
+ {% vite_asset 'src/entries/my_account.tsx' %}
-{% if has_characters %} -
-
-

Mis personajes

-
-
- {% for character in characters %} -
-
- - {{ character.name }} - -

{{ character.name }}

-

Nivel {{ character.level }}

- - -

- {{ character.zone }} -

- - -

- - {{ character.gold }} - {{ character.silver }} - {{ character.copper }} - -

-
-
- {% endfor %} -
-
-{% endif %}