diff --git a/frontend/src/components/SelectAccount.tsx b/frontend/src/components/SelectAccount.tsx new file mode 100644 index 0000000..d10d95b --- /dev/null +++ b/frontend/src/components/SelectAccount.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react' + +export interface GameAccount { + id: number + username: string + index: number +} + +interface Props { + accounts: GameAccount[] + selectUrl: string +} + +export function SelectAccount({ accounts, selectUrl }: Props) { + const [busy, setBusy] = useState(null) + const [error, setError] = useState(null) + + async function select(id: number) { + if (busy !== null) return + setBusy(id) + setError(null) + try { + const r = await fetch(`${selectUrl}?account_id=${id}`, { + headers: { Accept: 'application/json' }, + credentials: 'same-origin', + }) + const d: { success?: boolean; redirect?: string; error?: string } = await r.json() + if (d.success && d.redirect) { + window.location.href = d.redirect + } else { + setError(d.error || 'No se pudo seleccionar la cuenta.') + setBusy(null) + } + } catch { + setError('Error de red. Inténtalo de nuevo.') + setBusy(null) + } + } + + return ( + + + {accounts.map((a) => ( + + + + ))} + {error && ( + + + + )} + +
+ +
+ {error} +
+ ) +} diff --git a/frontend/src/entries/select_account.tsx b/frontend/src/entries/select_account.tsx new file mode 100644 index 0000000..6687737 --- /dev/null +++ b/frontend/src/entries/select_account.tsx @@ -0,0 +1,18 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { SelectAccount, type GameAccount } from '../components/SelectAccount' + +const dataEl = document.getElementById('select-account-data') +const accounts: GameAccount[] = dataEl ? JSON.parse(dataEl.textContent || '[]') : [] + +const el = document.getElementById('select-account-app') +if (el) { + createRoot(el).render( + + + , + ) +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 173de4b..7526e6c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -17,6 +17,7 @@ export default defineConfig({ home: resolve(__dirname, 'src/entries/home.tsx'), login: resolve(__dirname, 'src/entries/login.tsx'), register: resolve(__dirname, 'src/entries/register.tsx'), + select_account: resolve(__dirname, 'src/entries/select_account.tsx'), }, }, }, diff --git a/home/api_urls.py b/home/api_urls.py index ff8e5b3..82176b4 100644 --- a/home/api_urls.py +++ b/home/api_urls.py @@ -5,4 +5,5 @@ from . import views 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'), ] diff --git a/home/templates/auth/select_account.html b/home/templates/auth/select_account.html index 4f5ac19..c36b647 100644 --- a/home/templates/auth/select_account.html +++ b/home/templates/auth/select_account.html @@ -20,20 +20,12 @@ {% if game_accounts %}

Tu cuenta Battle.net tiene varias cuentas de juego. Elige con cuál quieres continuar:


-
- {% csrf_token %} - - {% for account in game_accounts %} - - - - {% endfor %} -
- -
-
+ {% load django_vite %} + +
+ {{ game_accounts|json_script:"select-account-data" }} + {% vite_asset 'src/entries/select_account.tsx' %} {% else %}

No se han encontrado cuentas de juego asociadas a esta cuenta Battle.net.


diff --git a/home/views/api.py b/home/views/api.py index bc898ca..d66f074 100644 --- a/home/views/api.py +++ b/home/views/api.py @@ -1,5 +1,6 @@ from ._base import * from .pages import check_server_status, get_online_characters_count +from .auth import _set_game_account_session # Endpoints JSON que alimentan las islas React/TSX del frontend (Vite). # Se montan bajo /api/ (fuera de i18n_patterns). De momento con JsonResponse; @@ -37,3 +38,25 @@ def home_status_api(request): "status": status, } ) + + +def account_select_api(request): + """Selecciona la cuenta de juego de la bnet en sesión (isla del selector). + + Vía GET (sin POST): es idempotente y solo permite elegir cuentas ligadas a la + propia cuenta Battle.net de la sesión, así que no expone nada de otros usuarios. + """ + bnet_id = request.session.get('bnet_id') + if not bnet_id: + return JsonResponse({"success": False, "error": "No autenticado"}, status=401) + try: + selected_id = int(request.GET.get("account_id", "")) + except (TypeError, ValueError): + return JsonResponse({"success": False, "error": "Cuenta no válida"}) + with connections['acore_auth'].cursor() as cursor: + accounts = bnet.get_game_accounts_for_bnet(cursor, bnet_id) + chosen = next((g for g in accounts if g["id"] == selected_id), None) + if not chosen: + return JsonResponse({"success": False, "error": "Cuenta de juego no válida."}) + _set_game_account_session(request, chosen) + return JsonResponse({"success": True, "redirect": reverse("my-account")}) diff --git a/home/views/auth.py b/home/views/auth.py index 4b758f5..d1d0c54 100644 --- a/home/views/auth.py +++ b/home/views/auth.py @@ -93,16 +93,7 @@ def select_account_view(request): with connections['acore_auth'].cursor() as cursor: game_accounts = bnet.get_game_accounts_for_bnet(cursor, bnet_id) - if request.method == 'POST': - try: - selected_id = int(request.POST.get('account_id', '')) - except (TypeError, ValueError): - selected_id = None - chosen = next((g for g in game_accounts if g['id'] == selected_id), None) - if chosen: - _set_game_account_session(request, chosen) - return redirect('my-account') - messages.error(request, 'Cuenta de juego no válida.') + # La selección la hace la isla React vía GET (/api/account/select/), sin POST. # Autoseleccionar si solo hay una if len(game_accounts) == 1: