From 4b6600e01f452925567182dc8479b6f61da6d723 Mon Sep 17 00:00:00 2001 From: kcar Date: Thu, 28 May 2026 12:44:10 +0100 Subject: [PATCH] fix auth redirects and API env naming --- .env.development | 1 - .env.example | 1 - src/AppRoutes.admin.test.tsx | 21 ++++++++++ src/AppRoutes.tsx | 20 +++++++--- src/axiosConfig.ts | 8 +--- src/config/api.ts | 14 +++++++ src/pages/Header.tsx | 38 +++++++++++++++++-- src/pages/auth/PlatformAdminPage.tsx | 2 +- src/pages/timetable/ClassDetailPage.tsx | 2 +- src/pages/timetable/LessonPlanDetailPage.tsx | 2 +- src/pages/timetable/LessonPlansPage.tsx | 2 +- src/pages/timetable/SchoolSettingsPage.tsx | 2 +- src/pages/timetable/StaffManagerPage.tsx | 2 +- src/pages/timetable/StudentLessonsPage.tsx | 2 +- src/pages/timetable/StudentManagerPage.tsx | 2 +- src/pages/timetable/TaughtLessonsPage.tsx | 2 +- src/pages/user/dashboardPage.tsx | 38 ++++++++++++++++++- src/services/timetableService.ts | 2 +- .../tldraw/ui-overrides/components/index.ts | 6 +-- .../shared/CCTranscriptionPanel.tsx | 2 +- 20 files changed, 137 insertions(+), 32 deletions(-) create mode 100644 src/config/api.ts 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) {