Skip to content

Commit 8cfa31f

Browse files
committed
feat: Introduce the Crucible sprints feature with database migrations, admin dashboards, and participant interfaces.
1 parent 16cd0d9 commit 8cfa31f

20 files changed

+4730
-8
lines changed

client/src/App.jsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ import CodeOfConduct from './pages/CodeOfConduct';
1414
// Admin imports
1515
import AdminLogin from './pages/admin/AdminLogin';
1616
import Dashboard from './pages/admin/Dashboard';
17+
import AdminEventDashboard from './pages/admin/AdminEventDashboard';
1718
import ProtectedRoute from './components/ProtectedRoute';
1819

20+
// Crucible imports
21+
import CrucibleAuth from './pages/crucible/CrucibleAuth';
22+
import ParticipantProtectedRoute from './components/ParticipantProtectedRoute';
23+
import EventDetailPage from './pages/crucible/EventDetailPage';
24+
import ParticipantDashboard from './pages/crucible/ParticipantDashboard';
25+
1926
export default function App() {
2027
const location = useLocation();
2128
const isAdminRoute = location.pathname.startsWith('/admin');
29+
const isCrucibleRoute = location.pathname.startsWith('/crucible');
2230

2331
// Admin routes render without the main site layout
2432
if (isAdminRoute) {
@@ -33,6 +41,29 @@ export default function App() {
3341
</ProtectedRoute>
3442
}
3543
/>
44+
<Route
45+
path="/admin/crucible/events/:slug"
46+
element={
47+
<ProtectedRoute>
48+
<AdminEventDashboard />
49+
</ProtectedRoute>
50+
}
51+
/>
52+
</Routes>
53+
);
54+
}
55+
56+
// Crucible routes render without header/footer (their own layout)
57+
if (isCrucibleRoute) {
58+
return (
59+
<Routes>
60+
<Route path="/crucible/auth" element={<CrucibleAuth />} />
61+
<Route path="/crucible/:slug" element={<EventDetailPage />} />
62+
<Route path="/crucible/dashboard" element={
63+
<ParticipantProtectedRoute>
64+
<ParticipantDashboard />
65+
</ParticipantProtectedRoute>
66+
} />
3667
</Routes>
3768
);
3869
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Navigate, useLocation } from 'react-router-dom';
2+
import { useAuth } from '../context/AuthContext';
3+
import { Loader2, Flame } from 'lucide-react';
4+
5+
export default function ParticipantProtectedRoute({ children }) {
6+
const { user, loading } = useAuth();
7+
const location = useLocation();
8+
9+
// Show loading spinner while checking auth
10+
if (loading) {
11+
return (
12+
<div className="min-h-screen bg-[#050505] flex items-center justify-center">
13+
<div className="text-center">
14+
<div className="w-12 h-12 mx-auto mb-4 rounded-xl bg-gradient-to-br from-orange-500/20 to-orange-600/20 flex items-center justify-center">
15+
<Flame className="w-6 h-6 text-orange-500 animate-pulse" />
16+
</div>
17+
<Loader2 className="w-6 h-6 text-orange-500 animate-spin mx-auto mb-3" />
18+
<p className="text-white/50 text-sm font-mono">Loading dashboard...</p>
19+
</div>
20+
</div>
21+
);
22+
}
23+
24+
// Redirect to crucible auth if not authenticated
25+
if (!user) {
26+
const redirectUrl = `/crucible/auth?redirect=${encodeURIComponent(location.pathname)}`;
27+
return <Navigate to={redirectUrl} replace />;
28+
}
29+
30+
return children;
31+
}

client/src/components/admin/AdminLayout.jsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ const navItems = [
3838
icon: Package,
3939
description: 'Portfolio'
4040
},
41+
{
42+
id: 'crucible',
43+
label: 'Crucible Sprints',
44+
icon: Flame,
45+
description: 'Event Management',
46+
children: [
47+
{ id: 'crucible-events', label: 'Events', icon: LayoutDashboard },
48+
{ id: 'crucible-participants', label: 'Participants', icon: Users },
49+
]
50+
},
4151
{
4252
id: 'settings',
4353
label: 'Settings',
@@ -101,8 +111,8 @@ export default function AdminLayout({ children, activeView, onViewChange }) {
101111
<button
102112
onClick={() => handleNavClick(item.id, !!item.children)}
103113
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all group ${isActive
104-
? 'bg-orange-500/10 text-orange-500'
105-
: 'text-white/50 hover:text-white hover:bg-white/5'
114+
? 'bg-orange-500/10 text-orange-500'
115+
: 'text-white/50 hover:text-white hover:bg-white/5'
106116
}`}
107117
>
108118
<Icon className="w-5 h-5 flex-shrink-0" />
@@ -131,8 +141,8 @@ export default function AdminLayout({ children, activeView, onViewChange }) {
131141
key={child.id}
132142
onClick={() => { onViewChange(child.id); setIsMobileMenuOpen(false); }}
133143
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-all text-sm ${activeView === child.id
134-
? 'bg-orange-500/10 text-orange-500'
135-
: 'text-white/40 hover:text-white hover:bg-white/5'
144+
? 'bg-orange-500/10 text-orange-500'
145+
: 'text-white/40 hover:text-white hover:bg-white/5'
136146
}`}
137147
>
138148
<ChildIcon className="w-4 h-4" />

0 commit comments

Comments
 (0)