feat: frontend auth — login/register modal + user badge

- API service (api.js): fetch wrapper with JWT, auto-refresh on 401
- AuthContext: user state, login/register/logout, loading, roles
- AuthModal: tabbed login/register form matching .pen design
- User badge in toolbar (Sandbox + WorldMap) with initial avatar
- "Entrar" button when not logged in
- CSS: auth overlay, card, tabs, inputs, error state, user badge
- Auth is opt-in: app works fully without login

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 20:22:43 +01:00
parent e129fd3739
commit 3523111019
7 changed files with 399 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
import React, { useState, useRef } from 'react';
import MobileTabBar from '../components/MobileTabBar.jsx';
import { useIsMobile } from '../hooks/useIsMobile.js';
import { useAuth } from '../services/AuthContext.jsx';
import { WORLD_1 } from './levels/world1.js';
import { WORLD_2 } from './levels/world2.js';
import { WORLD_3 } from './levels/world3.js';
@@ -53,6 +54,7 @@ export default function WorldMap({ onSelectLevel, onSandbox, onAdmin }) {
const [search, setSearch] = useState('');
const searchRef = useRef(null);
const isMobile = useIsMobile();
const { user, isLoggedIn, openAuth, logout } = useAuth();
const query = search.trim().toLowerCase();
@@ -90,6 +92,14 @@ export default function WorldMap({ onSelectLevel, onSandbox, onAdmin }) {
🛠
</button>
)}
{isLoggedIn ? (
<div className="user-badge" onClick={logout} title="Cerrar sesion">
<div className="user-avatar">{user.username?.[0]?.toUpperCase()}</div>
<span className="user-name">{user.username}</span>
</div>
) : (
<button className="login-btn" onClick={openAuth}>Entrar</button>
)}
</div>
</div>