feat: wire up /admin/* routing
Adds AdminApp as a lazy-loaded route subtree in App.tsx, mounting ProtectedRoute, AdminLogin, AdminLayout, and all five CRUD pages built in Tasks 3-11. Mounted as a sibling of the public Layout route (not nested under it), since the admin panel has its own AdminLayout sidebar rather than the public nav/footer. Manually verified end-to-end against the local Supabase stack: sign-in redirect guard, all five pages rendering real seeded data, creating and cancelling a real booking through the UI (confirming the booked_slots cleanup trigger from the merged Phase 2A follow-up fixes correctly frees the slot), and sign-out. No console errors across the flow. Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,3 +32,6 @@ dist-ssr
|
||||
# Supabase CLI local artifacts
|
||||
supabase/.branches
|
||||
supabase/.temp
|
||||
|
||||
# Playwright MCP scratch output (screenshots, console logs, page snapshots)
|
||||
.playwright-mcp/
|
||||
|
||||
@@ -6,6 +6,7 @@ import Book from './pages/Book'
|
||||
import Rules from './pages/Rules'
|
||||
|
||||
const Home = lazy(() => import('./pages/Home'))
|
||||
const AdminApp = lazy(() => import('./pages/admin/AdminApp'))
|
||||
|
||||
interface AppProps {
|
||||
initialEntries?: string[]
|
||||
@@ -27,6 +28,14 @@ function AppRoutes() {
|
||||
<Route path="/book" element={<Book />} />
|
||||
<Route path="/rules" element={<Rules />} />
|
||||
</Route>
|
||||
<Route
|
||||
path="/admin/*"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<AdminApp />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
42
src/pages/admin/AdminApp.test.tsx
Normal file
42
src/pages/admin/AdminApp.test.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MemoryRouter, Routes, Route } from 'react-router-dom'
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
|
||||
vi.mock('../../hooks/useAdminAuth', () => ({
|
||||
default: () => ({ session: { user: { id: 'admin' } }, loading: false }),
|
||||
}))
|
||||
vi.mock('../../lib/supabase', () => ({
|
||||
supabase: {
|
||||
auth: { signOut: vi.fn() },
|
||||
from: () => ({
|
||||
select: () => ({ order: () => Promise.resolve({ data: [], error: null }) }),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
import AdminApp from './AdminApp'
|
||||
|
||||
describe('AdminApp routing', () => {
|
||||
it('renders the login page at /admin/login', () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/admin/login']}>
|
||||
<Routes>
|
||||
<Route path="/admin/*" element={<AdminApp />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
expect(screen.getByRole('heading', { name: /admin login/i })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the products page (with layout) at /admin/products when authenticated', async () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/admin/products']}>
|
||||
<Routes>
|
||||
<Route path="/admin/*" element={<AdminApp />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
expect(await screen.findByRole('heading', { name: /^products$/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: /bookings/i })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
28
src/pages/admin/AdminApp.tsx
Normal file
28
src/pages/admin/AdminApp.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import ProtectedRoute from '../../components/ProtectedRoute'
|
||||
import AdminLogin from './AdminLogin'
|
||||
import AdminLayout from './AdminLayout'
|
||||
import AdminProducts from './AdminProducts'
|
||||
import AdminServices from './AdminServices'
|
||||
import AdminSchedule from './AdminSchedule'
|
||||
import AdminBookings from './AdminBookings'
|
||||
import AdminInquiries from './AdminInquiries'
|
||||
|
||||
function AdminApp() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="login" element={<AdminLogin />} />
|
||||
<Route element={<ProtectedRoute />}>
|
||||
<Route element={<AdminLayout />}>
|
||||
<Route path="products" element={<AdminProducts />} />
|
||||
<Route path="services" element={<AdminServices />} />
|
||||
<Route path="schedule" element={<AdminSchedule />} />
|
||||
<Route path="bookings" element={<AdminBookings />} />
|
||||
<Route path="inquiries" element={<AdminInquiries />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminApp
|
||||
Reference in New Issue
Block a user