diff --git a/.env.development b/.env.development
index 16607d6..815615d 100644
--- a/.env.development
+++ b/.env.development
@@ -18,7 +18,6 @@ VITE_APP_HMR_URL=http://192.168.0.94:5173
VITE_SUPABASE_URL=http://192.168.0.155:8000
# API should use localhost for local development
-VITE_API_URL=http://192.168.0.94:8000
VITE_API_BASE=http://192.168.0.94:8000
# Neo4j
diff --git a/.env.example b/.env.example
index 775b7af..95c7411 100644
--- a/.env.example
+++ b/.env.example
@@ -14,7 +14,6 @@ VITE_APP_HMR_URL=http://localhost:24678
# =============================================================================
# API Configuration
# =============================================================================
-VITE_API_URL=https://api.classroomcopilot.ai
VITE_API_BASE=https://api.classroomcopilot.ai
# =============================================================================
diff --git a/src/AppRoutes.admin.test.tsx b/src/AppRoutes.admin.test.tsx
index 96572a0..fcbc922 100644
--- a/src/AppRoutes.admin.test.tsx
+++ b/src/AppRoutes.admin.test.tsx
@@ -121,3 +121,24 @@ describe('/admin route authorization', () => {
expect(screen.getByText('Platform Admin Page')).toBeInTheDocument();
});
});
+
+
+describe('authenticated route redirects', () => {
+ beforeEach(() => {
+ mockUseAuth.mockReset();
+ });
+
+ it.each([
+ ['/calendar'],
+ ['/settings'],
+ ['/single-player'],
+ ['/lesson-plans'],
+ ])('redirects anonymous users from %s to login', (path) => {
+ mockUseAuth.mockReturnValue(authState({ user: null, user_role: null }));
+
+ renderAt(path);
+
+ expect(screen.getByText('Login Page')).toBeInTheDocument();
+ expect(screen.queryByText('Public Not Found')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx
index 85665b7..66e6fc9 100644
--- a/src/AppRoutes.tsx
+++ b/src/AppRoutes.tsx
@@ -11,7 +11,6 @@ import { CCExamMarker } from './pages/tldraw/CCExamMarker/CCExamMarker';
import CalendarPage from './pages/user/calendarPage';
import SettingsPage from './pages/user/settingsPage';
import TLDrawCanvas from './pages/tldraw/TLDrawCanvas';
-import AdminDashboard from './pages/auth/adminPage';
import PlatformAdminPage from './pages/auth/PlatformAdminPage';
import TLDrawDevPage from './pages/tldraw/devPlayerPage';
import DevPage from './pages/tldraw/devPage';
@@ -44,6 +43,17 @@ import {
LessonPlanDetailPage,
} from './pages/timetable';
+const ProtectedRoutes: React.FC = () => {
+ const { user } = useAuth();
+ const location = useLocation();
+
+ if (!user) {
+ return ;
+ }
+
+ return ;
+};
+
const FullContextRoutes: React.FC = () => {
// Only block on Supabase profile being ready — Neo4j contexts initialize in the background.
// Individual pages handle their own Neo4j loading states.
@@ -118,7 +128,7 @@ const AppRoutes: React.FC = () => {
{/* Lightweight authenticated routes */}
: }
+ element={user ? : }
/>
{/* Super Admin only routes */}
@@ -135,8 +145,8 @@ const AppRoutes: React.FC = () => {
}
/>
- {/* Authentication only routes - only render if all contexts are initialized */}
- {user && (
+ {/* Authentication only routes */}
+ }>
}>
{/* Timetable Module Routes */}
} />
@@ -167,7 +177,7 @@ const AppRoutes: React.FC = () => {
} />
} />
- )}
+
{/* Fallback route - use different NotFound pages based on auth state */}
: } />
diff --git a/src/axiosConfig.ts b/src/axiosConfig.ts
index 68aedc6..77a25bf 100644
--- a/src/axiosConfig.ts
+++ b/src/axiosConfig.ts
@@ -1,12 +1,8 @@
import axios from 'axios';
import { logger } from './debugConfig';
+import { API_BASE } from './config/api';
-// Use development backend URL if no custom URL is provided
-const baseURL = import.meta.env.VITE_API_BASE || 'http://localhost:8080';
-
-if (!import.meta.env.VITE_API_BASE) {
- logger.warn('axios', '⚠️ VITE_API_BASE not set, defaulting to http://localhost:8080');
-}
+const baseURL = API_BASE;
const instance = axios.create({
baseURL,
diff --git a/src/config/api.ts b/src/config/api.ts
new file mode 100644
index 0000000..93c34a6
--- /dev/null
+++ b/src/config/api.ts
@@ -0,0 +1,14 @@
+import { logger } from '../debugConfig';
+
+const DEFAULT_API_BASE = 'http://localhost:8080';
+
+const normalizeApiBase = (value: string | undefined): string => {
+ const trimmed = value?.trim();
+ return trimmed ? trimmed.replace(/\/$/, '') : DEFAULT_API_BASE;
+};
+
+export const API_BASE = normalizeApiBase(import.meta.env.VITE_API_BASE);
+
+if (!import.meta.env.VITE_API_BASE) {
+ logger.warn('api-config', `⚠️ VITE_API_BASE not set, defaulting to ${DEFAULT_API_BASE}`);
+}
diff --git a/src/pages/Header.tsx b/src/pages/Header.tsx
index c0a7e5f..037e3f9 100644
--- a/src/pages/Header.tsx
+++ b/src/pages/Header.tsx
@@ -34,11 +34,12 @@ import Lessons from '@mui/icons-material/EventNote';
import LessonPlans from '@mui/icons-material/LibraryBooks';
import People from '@mui/icons-material/People';
import SchoolSettings from '@mui/icons-material/Tune';
+import AddBusiness from '@mui/icons-material/AddBusiness';
import { HEADER_HEIGHT } from './Layout';
import { logger } from '../debugConfig';
import { GraphNavigator } from '../components/navigation/GraphNavigator';
-
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { SchoolOnboardingWizard } from '../utils/tldraw/ui-overrides/components/shared/navigation/SchoolOnboardingWizard';
+import { API_BASE } from '../config/api';
const Header: React.FC = () => {
const theme = useTheme();
@@ -48,6 +49,8 @@ const Header: React.FC = () => {
const [anchorEl, setAnchorEl] = useState(null);
const [isPlatformAdmin, setIsPlatformAdmin] = useState(false);
const [schoolRole, setSchoolRole] = useState(null);
+ const [schoolStatus, setSchoolStatus] = useState(null);
+ const [onboardingOpen, setOnboardingOpen] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(!!user);
const showGraphNavigation = location.pathname === '/single-player';
const isSchoolAdmin = schoolRole === 'school_admin' || schoolRole === 'department_head';
@@ -76,15 +79,17 @@ const Header: React.FC = () => {
if (res.ok) {
const data = await res.json();
setSchoolRole(data.user_role || null);
+ setSchoolStatus(data.status || null);
}
} catch {
setSchoolRole(null);
+ setSchoolStatus(null);
}
}, [accessToken]);
useEffect(() => {
if (accessToken) checkAdminStatus();
- else { setIsPlatformAdmin(false); setSchoolRole(null); }
+ else { setIsPlatformAdmin(false); setSchoolRole(null); setSchoolStatus(null); }
}, [accessToken, checkAdminStatus]);
const handleMenuOpen = (event: React.MouseEvent) => {
@@ -100,6 +105,16 @@ const Header: React.FC = () => {
handleMenuClose();
};
+ const handleOnboardingOpen = () => {
+ setOnboardingOpen(true);
+ handleMenuClose();
+ };
+
+ const handleOnboardingComplete = () => {
+ setOnboardingOpen(false);
+ void checkAdminStatus();
+ };
+
const handleSignupNavigation = (role: 'teacher' | 'student') => {
navigate('/signup', { state: { role } });
handleMenuClose();
@@ -119,6 +134,7 @@ const Header: React.FC = () => {
};
return (
+ <>
{
secondary="Plan and manage lessons"
/>
,
+ ,
,
// School Admin Section
@@ -357,6 +382,13 @@ const Header: React.FC = () => {
+ setOnboardingOpen(false)}
+ onComplete={handleOnboardingComplete}
+ apiBase={API_BASE}
+ />
+ >
);
};
diff --git a/src/pages/auth/PlatformAdminPage.tsx b/src/pages/auth/PlatformAdminPage.tsx
index e973b00..778b436 100644
--- a/src/pages/auth/PlatformAdminPage.tsx
+++ b/src/pages/auth/PlatformAdminPage.tsx
@@ -8,7 +8,7 @@ import { Refresh, School, People, EventNote, HourglassEmpty } from '@mui/icons-m
import { useNavigate } from 'react-router';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
interface SchoolEntry {
id: string;
diff --git a/src/pages/timetable/ClassDetailPage.tsx b/src/pages/timetable/ClassDetailPage.tsx
index b7a4300..28ee21d 100644
--- a/src/pages/timetable/ClassDetailPage.tsx
+++ b/src/pages/timetable/ClassDetailPage.tsx
@@ -10,7 +10,7 @@ import {
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
// ─── Types ────────────────────────────────────────────────────────────────────
diff --git a/src/pages/timetable/LessonPlanDetailPage.tsx b/src/pages/timetable/LessonPlanDetailPage.tsx
index 6f8fd3d..cfb9be8 100644
--- a/src/pages/timetable/LessonPlanDetailPage.tsx
+++ b/src/pages/timetable/LessonPlanDetailPage.tsx
@@ -12,7 +12,7 @@ import {
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
// ─── Types ────────────────────────────────────────────────────────────────────
diff --git a/src/pages/timetable/LessonPlansPage.tsx b/src/pages/timetable/LessonPlansPage.tsx
index 5855eea..14ca3c0 100644
--- a/src/pages/timetable/LessonPlansPage.tsx
+++ b/src/pages/timetable/LessonPlansPage.tsx
@@ -9,7 +9,7 @@ import {
import { Add, Search, LibraryBooks, Edit, Delete, FilterList } from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
// ─── Types ────────────────────────────────────────────────────────────────────
diff --git a/src/pages/timetable/SchoolSettingsPage.tsx b/src/pages/timetable/SchoolSettingsPage.tsx
index a62585d..2f238b9 100644
--- a/src/pages/timetable/SchoolSettingsPage.tsx
+++ b/src/pages/timetable/SchoolSettingsPage.tsx
@@ -13,7 +13,7 @@ import {
import { useNavigate } from 'react-router';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
const DAY_TYPE_OPTIONS = ['Academic', 'Holiday', 'Staff', 'OffTimetable'];
const DAY_TYPE_COLORS: Record = {
diff --git a/src/pages/timetable/StaffManagerPage.tsx b/src/pages/timetable/StaffManagerPage.tsx
index e87888b..b878d94 100644
--- a/src/pages/timetable/StaffManagerPage.tsx
+++ b/src/pages/timetable/StaffManagerPage.tsx
@@ -10,7 +10,7 @@ import {
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
// ─── Types ────────────────────────────────────────────────────────────────────
diff --git a/src/pages/timetable/StudentLessonsPage.tsx b/src/pages/timetable/StudentLessonsPage.tsx
index c21e3e7..06d4894 100644
--- a/src/pages/timetable/StudentLessonsPage.tsx
+++ b/src/pages/timetable/StudentLessonsPage.tsx
@@ -5,7 +5,7 @@ import {
import { ChevronLeft, ChevronRight, Today } from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
// ─── Types ────────────────────────────────────────────────────────────────────
diff --git a/src/pages/timetable/StudentManagerPage.tsx b/src/pages/timetable/StudentManagerPage.tsx
index 202e5c5..0d3c3dd 100644
--- a/src/pages/timetable/StudentManagerPage.tsx
+++ b/src/pages/timetable/StudentManagerPage.tsx
@@ -9,7 +9,7 @@ import {
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
interface Student {
profile_id: string;
diff --git a/src/pages/timetable/TaughtLessonsPage.tsx b/src/pages/timetable/TaughtLessonsPage.tsx
index ffccb49..f94a61b 100644
--- a/src/pages/timetable/TaughtLessonsPage.tsx
+++ b/src/pages/timetable/TaughtLessonsPage.tsx
@@ -12,7 +12,7 @@ import {
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../../config/api';
// ─── Types ────────────────────────────────────────────────────────────────────
diff --git a/src/pages/user/dashboardPage.tsx b/src/pages/user/dashboardPage.tsx
index cc34e1e..4c5bfba 100644
--- a/src/pages/user/dashboardPage.tsx
+++ b/src/pages/user/dashboardPage.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
@@ -6,16 +6,21 @@ import {
Container,
Grid,
Paper,
+ Alert,
Stack,
Typography
} from '@mui/material';
import { useAuth } from '../../contexts/AuthContext';
import { useUser } from '../../contexts/UserContext';
+import { API_BASE } from '../../config/api';
+import { SchoolOnboardingWizard } from '../../utils/tldraw/ui-overrides/components/shared/navigation/SchoolOnboardingWizard';
const DashboardPage: React.FC = () => {
const navigate = useNavigate();
const { user: authUser } = useAuth();
const { profile, loading } = useUser();
+ const [schoolOnboardingOpen, setSchoolOnboardingOpen] = useState(false);
+ const [schoolSetupComplete, setSchoolSetupComplete] = useState(false);
const displayName = profile?.display_name || authUser?.display_name || authUser?.username || 'Member';
const emailAddress = profile?.email || authUser?.email || '';
@@ -87,8 +92,39 @@ const DashboardPage: React.FC = () => {
+
+
+
+
+
+ School onboarding
+
+
+ Link your account to a school, confirm school details, and set up an academic calendar without opening the canvas navigation panel first.
+
+ {schoolSetupComplete && (
+
+ School setup completed. Refreshing navigation will pick up the new school context.
+
+ )}
+
+
+
+
+
+ setSchoolOnboardingOpen(false)}
+ onComplete={() => {
+ setSchoolOnboardingOpen(false);
+ setSchoolSetupComplete(true);
+ }}
+ apiBase={API_BASE}
+ />
);
};
diff --git a/src/services/timetableService.ts b/src/services/timetableService.ts
index ad4e7c9..28e4beb 100644
--- a/src/services/timetableService.ts
+++ b/src/services/timetableService.ts
@@ -15,7 +15,7 @@ import {
} from '../types/timetable.types';
// API base URL
-const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
+import { API_BASE } from '../config/api';
// ============================================================================
// Helper: Get auth headers
diff --git a/src/utils/tldraw/ui-overrides/components/index.ts b/src/utils/tldraw/ui-overrides/components/index.ts
index 94fa433..0702e84 100644
--- a/src/utils/tldraw/ui-overrides/components/index.ts
+++ b/src/utils/tldraw/ui-overrides/components/index.ts
@@ -4,7 +4,6 @@ import { RegularToolbar } from './regular/toolbar';
import { RegularHelperButtons } from './regular/helperButton';
// import { RegularHelpMenu } from './regular/helpMenu';
// import { RegularMainMenu } from './regular/mainMenu';
-import { RegularNavigationPanel } from './regular/navigationPanel';
// import { RegularPageMenu } from './regular/pageMenu';
// import { RegularQuickActions } from './regular/quickActions';
import { RegularStylePanel } from './regular/stylePanel';
@@ -18,7 +17,6 @@ import { PresentationToolbar } from './presentation/toolbar';
import { PresentationHelperButtons } from './presentation/helperButton';
// import { PresentationHelpMenu } from './presentation/helpMenu';
// import { PresentationMainMenu } from './presentation/mainMenu';
-import { PresentationNavigationPanel } from './presentation/navigationPanel';
// import { PresentationPageMenu } from './presentation/pageMenu';
import { PresentationQuickActions } from './presentation/quickActions';
import { PresentationStylePanel } from './presentation/stylePanel';
@@ -35,7 +33,7 @@ export const regularComponentsIndex: TLComponents = {
HelperButtons: RegularHelperButtons,
// HelpMenu: RegularHelpMenu,
// MainMenu: RegularMainMenu,
- NavigationPanel: RegularNavigationPanel,
+ // NavigationPanel intentionally omitted; CCPanel/CCGraphNavPanel is the supported navigation surface.
// PageMenu: RegularPageMenu,
// QuickActions: RegularQuickActions,
StylePanel: RegularStylePanel,
@@ -52,7 +50,7 @@ export const presentationComponentsIndex: TLComponents = {
HelperButtons: PresentationHelperButtons,
// HelpMenu: PresentationHelpMenu,
// MainMenu: PresentationMainMenu,
- NavigationPanel: PresentationNavigationPanel,
+ // NavigationPanel intentionally omitted; CCPanel/CCGraphNavPanel is the supported navigation surface.
// PageMenu: PresentationPageMenu,
QuickActions: PresentationQuickActions,
StylePanel: PresentationStylePanel,
diff --git a/src/utils/tldraw/ui-overrides/components/shared/CCTranscriptionPanel.tsx b/src/utils/tldraw/ui-overrides/components/shared/CCTranscriptionPanel.tsx
index 300b8a7..fdcfdba 100644
--- a/src/utils/tldraw/ui-overrides/components/shared/CCTranscriptionPanel.tsx
+++ b/src/utils/tldraw/ui-overrides/components/shared/CCTranscriptionPanel.tsx
@@ -119,7 +119,7 @@ export const CCTranscriptionPanel: React.FC = () => {
useEffect(() => {
const detectTimetable = async () => {
try {
- const apiBase = import.meta.env.VITE_API_URL || 'https://api.classroomcopilot.ai';
+ const apiBase = API_BASE;
const response = await fetch(`${apiBase}/database/timetables/current-period`);
const data = await response.json();
if (data.period_id) {