feat: Phase 4 — Admin panel (dashboard, users, moderation)
AdminPanel2 component with sidebar navigation: - Dashboard: KPI cards (users, patches, premium, flagged) - Users: search, filter by role, table with role dropdown to change user/premium/admin/banned per user - Workshop moderation: filter flagged/deleted, approve/delete/restore actions per patch with status badges Features: - Role-protected: non-admins see 🔒 locked screen - Sidebar nav: Dashboard / Usuarios / Workshop / Volver - Admin button visible in Workshop nav for admin users - Responsive: sidebar becomes horizontal tabs on mobile, KPIs 2x2 grid, table rows wrap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
244
packages/client/src/components/AdminPanel2.jsx
Normal file
244
packages/client/src/components/AdminPanel2.jsx
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { admin as adminApi } from '../services/api.js';
|
||||||
|
import { useAuth } from '../services/AuthContext.jsx';
|
||||||
|
|
||||||
|
function Sidebar({ active, onNavigate, onBack }) {
|
||||||
|
const items = [
|
||||||
|
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
|
||||||
|
{ id: 'users', icon: '👥', label: 'Usuarios' },
|
||||||
|
{ id: 'workshop', icon: '🎛', label: 'Workshop' },
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="adm-sidebar">
|
||||||
|
<div className="adm-sidebar-logo">
|
||||||
|
<div className="auth-logo-box" style={{ width: 28, height: 28, fontSize: 14 }}>~</div>
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)' }}>Admin</span>
|
||||||
|
</div>
|
||||||
|
{items.map(item => (
|
||||||
|
<button key={item.id}
|
||||||
|
className={`adm-sidebar-item ${active === item.id ? 'active' : ''}`}
|
||||||
|
onClick={() => onNavigate(item.id)}>
|
||||||
|
<span>{item.icon}</span> {item.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<button className="adm-sidebar-item" onClick={onBack}>
|
||||||
|
← Volver a la app
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DashboardView() {
|
||||||
|
const [stats, setStats] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
adminApi.stats().then(setStats).catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!stats) return <p style={{ color: 'var(--text2)' }}>Cargando...</p>;
|
||||||
|
|
||||||
|
const kpis = [
|
||||||
|
{ label: 'USUARIOS TOTALES', value: stats.users, color: 'var(--text)' },
|
||||||
|
{ label: 'PATCHES COMPARTIDOS', value: stats.patches, color: 'var(--text)' },
|
||||||
|
{ label: 'PREMIUM', value: stats.premium, color: 'var(--yellow)' },
|
||||||
|
{ label: 'REPORTADOS', value: stats.flagged, color: 'var(--red)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="adm-page-title">Dashboard</h2>
|
||||||
|
<div className="adm-kpi-grid">
|
||||||
|
{kpis.map(k => (
|
||||||
|
<div key={k.label} className="adm-kpi-card">
|
||||||
|
<span className="adm-kpi-label">{k.label}</span>
|
||||||
|
<span className="adm-kpi-value" style={{ color: k.color }}>{k.value}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UsersView() {
|
||||||
|
const [users, setUsers] = useState([]);
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
const [filter, setFilter] = useState('');
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (search) params.set('q', search);
|
||||||
|
if (filter) params.set('role', filter);
|
||||||
|
const data = await adminApi.users(params.toString());
|
||||||
|
setUsers(data.users || []);
|
||||||
|
}, [search, filter]);
|
||||||
|
|
||||||
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
const changeRole = async (id, role) => {
|
||||||
|
await adminApi.updateUser(id, { role });
|
||||||
|
load();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="adm-page-title">Usuarios</h2>
|
||||||
|
<div className="adm-toolbar">
|
||||||
|
<div className="ws-search" style={{ flex: 1 }}>
|
||||||
|
<span>🔍</span>
|
||||||
|
<input placeholder="Buscar usuario..." value={search}
|
||||||
|
onChange={e => setSearch(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="ws-tags">
|
||||||
|
<button className={`ws-tag ${!filter ? 'active' : ''}`} onClick={() => setFilter('')}>Todos</button>
|
||||||
|
<button className={`ws-tag ${filter === 'premium' ? 'active' : ''}`} onClick={() => setFilter('premium')} style={{ color: 'var(--yellow)' }}>Premium</button>
|
||||||
|
<button className={`ws-tag ${filter === 'banned' ? 'active' : ''}`} onClick={() => setFilter('banned')} style={{ color: 'var(--red)' }}>Banned</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="adm-table">
|
||||||
|
<div className="adm-table-head">
|
||||||
|
<span className="adm-col-grow">USUARIO</span>
|
||||||
|
<span className="adm-col-md">EMAIL</span>
|
||||||
|
<span className="adm-col-sm">ROL</span>
|
||||||
|
<span className="adm-col-sm">REGISTRO</span>
|
||||||
|
<span className="adm-col-xs">ACCIONES</span>
|
||||||
|
</div>
|
||||||
|
{users.map(u => (
|
||||||
|
<div key={u.id} className={`adm-table-row ${u.role === 'banned' ? 'banned' : ''}`}>
|
||||||
|
<div className="adm-col-grow adm-user-cell">
|
||||||
|
<div className="user-avatar" style={{ background: u.role === 'banned' ? 'var(--red)' : 'var(--accent)' }}>
|
||||||
|
{u.username?.[0]?.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<span>{u.username}</span>
|
||||||
|
</div>
|
||||||
|
<span className="adm-col-md adm-text-muted">{u.email}</span>
|
||||||
|
<span className="adm-col-sm">
|
||||||
|
<span className={`adm-role-badge ${u.role}`}>
|
||||||
|
{u.role === 'premium' ? '★ ' : u.role === 'banned' ? '🚫 ' : ''}{u.role}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span className="adm-col-sm adm-text-muted">
|
||||||
|
{new Date(u.createdAt).toLocaleDateString('es')}
|
||||||
|
</span>
|
||||||
|
<div className="adm-col-xs">
|
||||||
|
<select className="adm-action-select" value={u.role}
|
||||||
|
onChange={e => changeRole(u.id, e.target.value)}>
|
||||||
|
<option value="user">User</option>
|
||||||
|
<option value="premium">Premium</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
<option value="banned">Banned</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function WorkshopModView() {
|
||||||
|
const [patches, setPatches] = useState([]);
|
||||||
|
const [filter, setFilter] = useState('');
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filter === 'flagged') params.set('flagged', 'true');
|
||||||
|
if (filter === 'deleted') params.set('deleted', 'true');
|
||||||
|
const data = await adminApi.patches(params.toString());
|
||||||
|
setPatches(data.patches || []);
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
const moderate = async (id, action) => {
|
||||||
|
await adminApi.updatePatch(id, { action });
|
||||||
|
load();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="adm-page-title">Workshop — Moderacion</h2>
|
||||||
|
<div className="adm-toolbar">
|
||||||
|
<div className="ws-tags">
|
||||||
|
<button className={`ws-tag ${!filter ? 'active' : ''}`} onClick={() => setFilter('')}>Todos</button>
|
||||||
|
<button className={`ws-tag ${filter === 'flagged' ? 'active' : ''}`}
|
||||||
|
onClick={() => setFilter('flagged')} style={{ color: 'var(--yellow)' }}>⚠ Reportados</button>
|
||||||
|
<button className={`ws-tag ${filter === 'deleted' ? 'active' : ''}`}
|
||||||
|
onClick={() => setFilter('deleted')} style={{ color: 'var(--red)' }}>🚫 Eliminados</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="adm-table">
|
||||||
|
<div className="adm-table-head">
|
||||||
|
<span className="adm-col-grow">PATCH</span>
|
||||||
|
<span className="adm-col-sm">LIKES</span>
|
||||||
|
<span className="adm-col-sm">ESTADO</span>
|
||||||
|
<span className="adm-col-xs">ACCIONES</span>
|
||||||
|
</div>
|
||||||
|
{patches.map(p => (
|
||||||
|
<div key={p.id} className={`adm-table-row ${p.isDeleted ? 'banned' : ''}`}>
|
||||||
|
<div className="adm-col-grow">
|
||||||
|
<strong style={{ color: 'var(--text)' }}>{p.title}</strong>
|
||||||
|
{p.tags?.length > 0 && (
|
||||||
|
<div style={{ display: 'flex', gap: 4, marginTop: 4 }}>
|
||||||
|
{p.tags.map(t => <span key={t} className="ws-tag-pill">{t}</span>)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="adm-col-sm" style={{ color: 'var(--red)' }}>♥ {p.likesCount}</span>
|
||||||
|
<span className="adm-col-sm">
|
||||||
|
{p.isDeleted
|
||||||
|
? <span className="adm-role-badge banned">🚫 Eliminado</span>
|
||||||
|
: p.isFlagged
|
||||||
|
? <span className="adm-role-badge" style={{ background: 'rgba(255,204,0,0.15)', color: 'var(--yellow)' }}>⚠ Reportado</span>
|
||||||
|
: <span className="adm-role-badge" style={{ background: 'rgba(68,255,136,0.15)', color: 'var(--green)' }}>✓ Activo</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<div className="adm-col-xs adm-actions">
|
||||||
|
{p.isDeleted ? (
|
||||||
|
<button className="adm-act-btn green" onClick={() => moderate(p.id, 'restore')}>Restaurar</button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{p.isFlagged && <button className="adm-act-btn green" onClick={() => moderate(p.id, 'unflag')}>Aprobar</button>}
|
||||||
|
<button className="adm-act-btn red" onClick={() => moderate(p.id, 'delete')}>Eliminar</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{patches.length === 0 && (
|
||||||
|
<p style={{ padding: 20, color: 'var(--text2)', textAlign: 'center' }}>No hay patches</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AdminPanel2({ onBack }) {
|
||||||
|
const { isAdmin } = useAuth();
|
||||||
|
const [page, setPage] = useState('dashboard');
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh', background: 'var(--bg)' }}>
|
||||||
|
<div style={{ textAlign: 'center', color: 'var(--text2)' }}>
|
||||||
|
<p style={{ fontSize: 48 }}>🔒</p>
|
||||||
|
<p>Acceso restringido a administradores</p>
|
||||||
|
<button className="login-btn" onClick={onBack} style={{ marginTop: 16 }}>Volver</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="adm-layout">
|
||||||
|
<Sidebar active={page} onNavigate={setPage} onBack={onBack} />
|
||||||
|
<div className="adm-main">
|
||||||
|
{page === 'dashboard' && <DashboardView />}
|
||||||
|
{page === 'users' && <UsersView />}
|
||||||
|
{page === 'workshop' && <WorkshopModView />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -142,8 +142,8 @@ function PatchCard({ patch, onLoad, onLike }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Workshop({ onSwitchToSandbox, onSwitchToGame }) {
|
export default function Workshop({ onSwitchToSandbox, onSwitchToGame, onSwitchToAdmin }) {
|
||||||
const { isLoggedIn, openAuth, logout, user } = useAuth();
|
const { isLoggedIn, isAdmin, openAuth, logout, user } = useAuth();
|
||||||
const [patches, setPatches] = useState([]);
|
const [patches, setPatches] = useState([]);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [activeTag, setActiveTag] = useState('');
|
const [activeTag, setActiveTag] = useState('');
|
||||||
@@ -209,6 +209,9 @@ export default function Workshop({ onSwitchToSandbox, onSwitchToGame }) {
|
|||||||
<button className="ws-nav-tab active">Workshop</button>
|
<button className="ws-nav-tab active">Workshop</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }} />
|
<div style={{ flex: 1 }} />
|
||||||
|
{isAdmin && onSwitchToAdmin && (
|
||||||
|
<button className="ws-nav-tab" onClick={onSwitchToAdmin} style={{ color: 'var(--yellow)' }}>🛠 Admin</button>
|
||||||
|
)}
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<div className="user-badge" onClick={logout} title="Cerrar sesion">
|
<div className="user-badge" onClick={logout} title="Cerrar sesion">
|
||||||
<div className="user-avatar">{user?.username?.[0]?.toUpperCase()}</div>
|
<div className="user-avatar">{user?.username?.[0]?.toUpperCase()}</div>
|
||||||
|
|||||||
@@ -1026,6 +1026,100 @@ input, textarea, [contenteditable] { -webkit-user-select: text; user-select: tex
|
|||||||
.auth-card { padding: 24px 20px; max-height: 90vh; overflow-y: auto; }
|
.auth-card { padding: 24px 20px; max-height: 90vh; overflow-y: auto; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Admin Panel v2 ===== */
|
||||||
|
.adm-layout { display: flex; height: 100vh; background: var(--bg); }
|
||||||
|
.adm-sidebar {
|
||||||
|
width: 220px; background: var(--panel); display: flex; flex-direction: column;
|
||||||
|
padding: 20px 16px; gap: 4px; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.adm-sidebar-logo { display: flex; align-items: center; gap: 8px; padding-bottom: 16px; }
|
||||||
|
.adm-sidebar-item {
|
||||||
|
display: flex; align-items: center; gap: 10px; padding: 10px 12px;
|
||||||
|
border: none; border-radius: 6px; background: transparent; color: var(--text2);
|
||||||
|
font-size: 13px; font-weight: 500; cursor: pointer; font-family: inherit;
|
||||||
|
text-align: left; width: 100%; transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.adm-sidebar-item:hover { color: var(--text); }
|
||||||
|
.adm-sidebar-item.active { background: var(--surface); color: var(--accent); font-weight: 600; }
|
||||||
|
|
||||||
|
.adm-main { flex: 1; padding: 24px 32px; overflow-y: auto; }
|
||||||
|
.adm-page-title { font-size: 24px; font-weight: 700; color: var(--text); margin: 0 0 20px; }
|
||||||
|
|
||||||
|
.adm-kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
|
||||||
|
.adm-kpi-card {
|
||||||
|
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
||||||
|
padding: 20px; display: flex; flex-direction: column; gap: 4px;
|
||||||
|
}
|
||||||
|
.adm-kpi-label { font-size: 10px; font-weight: 700; color: var(--text2); letter-spacing: 1px; }
|
||||||
|
.adm-kpi-value { font-size: 32px; font-weight: 700; font-family: 'JetBrains Mono', monospace; }
|
||||||
|
|
||||||
|
.adm-toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }
|
||||||
|
|
||||||
|
.adm-table {
|
||||||
|
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.adm-table-head {
|
||||||
|
display: flex; padding: 12px 20px; gap: 12px; align-items: center;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.adm-table-head span {
|
||||||
|
font-size: 10px; font-weight: 700; color: var(--text2); letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
.adm-table-row {
|
||||||
|
display: flex; padding: 10px 20px; gap: 12px; align-items: center;
|
||||||
|
border-bottom: 1px solid rgba(37,37,69,0.5);
|
||||||
|
}
|
||||||
|
.adm-table-row:last-child { border-bottom: none; }
|
||||||
|
.adm-table-row.banned { opacity: 0.4; }
|
||||||
|
|
||||||
|
.adm-col-grow { flex: 1; min-width: 0; }
|
||||||
|
.adm-col-md { width: 200px; flex-shrink: 0; }
|
||||||
|
.adm-col-sm { width: 120px; flex-shrink: 0; }
|
||||||
|
.adm-col-xs { width: 100px; flex-shrink: 0; }
|
||||||
|
.adm-text-muted { font-size: 12px; color: var(--text2); }
|
||||||
|
|
||||||
|
.adm-user-cell { display: flex; align-items: center; gap: 8px; }
|
||||||
|
.adm-user-cell span { font-size: 12px; font-weight: 500; color: var(--text); }
|
||||||
|
|
||||||
|
.adm-role-badge {
|
||||||
|
display: inline-block; padding: 3px 8px; border-radius: 10px;
|
||||||
|
font-size: 10px; font-weight: 600;
|
||||||
|
}
|
||||||
|
.adm-role-badge.user { background: rgba(100,116,139,0.15); color: var(--text2); }
|
||||||
|
.adm-role-badge.premium { background: rgba(255,204,0,0.15); color: var(--yellow); }
|
||||||
|
.adm-role-badge.admin { background: rgba(0,229,255,0.15); color: var(--accent); }
|
||||||
|
.adm-role-badge.banned { background: rgba(255,68,102,0.15); color: var(--red); }
|
||||||
|
|
||||||
|
.adm-action-select {
|
||||||
|
padding: 4px 8px; background: var(--bg); border: 1px solid var(--border);
|
||||||
|
border-radius: 4px; color: var(--text); font-size: 11px; cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.adm-action-select:focus { outline: none; border-color: var(--accent); }
|
||||||
|
|
||||||
|
.adm-actions { display: flex; gap: 6px; }
|
||||||
|
.adm-act-btn {
|
||||||
|
padding: 4px 10px; border-radius: 4px; border: 1px solid var(--border);
|
||||||
|
background: var(--bg); cursor: pointer; font-size: 10px; font-family: inherit;
|
||||||
|
}
|
||||||
|
.adm-act-btn.green { border-color: var(--green); color: var(--green); }
|
||||||
|
.adm-act-btn.red { border-color: var(--red); color: var(--red); }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.adm-layout { flex-direction: column; }
|
||||||
|
.adm-sidebar { width: 100%; flex-direction: row; padding: 8px 12px; gap: 4px; overflow-x: auto; }
|
||||||
|
.adm-sidebar-logo { display: none; }
|
||||||
|
.adm-sidebar-item { white-space: nowrap; padding: 8px 12px; font-size: 12px; }
|
||||||
|
.adm-main { padding: 16px; }
|
||||||
|
.adm-kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
|
||||||
|
.adm-kpi-value { font-size: 24px; }
|
||||||
|
.adm-table-head { display: none; }
|
||||||
|
.adm-table-row { flex-wrap: wrap; gap: 6px; }
|
||||||
|
.adm-col-md, .adm-col-sm, .adm-col-xs { width: auto; flex-shrink: 1; }
|
||||||
|
.adm-col-grow { width: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Fullscreen Keyboard ===== */
|
/* ===== Fullscreen Keyboard ===== */
|
||||||
.keyboard-fullscreen {
|
.keyboard-fullscreen {
|
||||||
position: fixed; inset: 0; z-index: 500;
|
position: fixed; inset: 0; z-index: 500;
|
||||||
|
|||||||
@@ -3,24 +3,27 @@ import { createRoot } from 'react-dom/client';
|
|||||||
import App from './App';
|
import App from './App';
|
||||||
import GameApp from './game/GameApp.jsx';
|
import GameApp from './game/GameApp.jsx';
|
||||||
import Workshop from './components/Workshop.jsx';
|
import Workshop from './components/Workshop.jsx';
|
||||||
|
import AdminPanel2 from './components/AdminPanel2.jsx';
|
||||||
import { AuthProvider } from './services/AuthContext.jsx';
|
import { AuthProvider } from './services/AuthContext.jsx';
|
||||||
import AuthModal from './components/AuthModal.jsx';
|
import AuthModal from './components/AuthModal.jsx';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
function Root() {
|
function Root() {
|
||||||
const [mode, setMode] = useState('game'); // 'game' | 'sandbox' | 'workshop'
|
const [mode, setMode] = useState('game'); // 'game' | 'sandbox' | 'workshop' | 'admin'
|
||||||
|
|
||||||
const nav = {
|
const nav = {
|
||||||
toGame: () => setMode('game'),
|
toGame: () => setMode('game'),
|
||||||
toSandbox: () => setMode('sandbox'),
|
toSandbox: () => setMode('sandbox'),
|
||||||
toWorkshop: () => setMode('workshop'),
|
toWorkshop: () => setMode('workshop'),
|
||||||
|
toAdmin: () => setMode('admin'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
{mode === 'sandbox' && <App onSwitchToGame={nav.toGame} onSwitchToWorkshop={nav.toWorkshop} />}
|
{mode === 'sandbox' && <App onSwitchToGame={nav.toGame} onSwitchToWorkshop={nav.toWorkshop} />}
|
||||||
{mode === 'game' && <GameApp onSwitchToSandbox={nav.toSandbox} onSwitchToWorkshop={nav.toWorkshop} />}
|
{mode === 'game' && <GameApp onSwitchToSandbox={nav.toSandbox} onSwitchToWorkshop={nav.toWorkshop} />}
|
||||||
{mode === 'workshop' && <Workshop onSwitchToSandbox={nav.toSandbox} onSwitchToGame={nav.toGame} />}
|
{mode === 'workshop' && <Workshop onSwitchToSandbox={nav.toSandbox} onSwitchToGame={nav.toGame} onSwitchToAdmin={nav.toAdmin} />}
|
||||||
|
{mode === 'admin' && <AdminPanel2 onBack={nav.toGame} />}
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user