diff --git a/.env.development b/.env.development index 16607d6..6339405 100644 --- a/.env.development +++ b/.env.development @@ -37,4 +37,4 @@ VITE_MICROSOFT_CLIENT_SECRET_ID=dummy_secret_id VITE_MICROSOFT_CLIENT_SECRET=dummy_secret VITE_MICROSOFT_TENANT_ID=common -VITE_SEARCH_URL=http://localhost:8888 +VITE_SEARCH_URL=https://search.kevlarai.com diff --git a/src/axiosConfig.ts b/src/axiosConfig.ts index 68aedc6..59afa5c 100644 --- a/src/axiosConfig.ts +++ b/src/axiosConfig.ts @@ -1,11 +1,12 @@ import axios from 'axios'; +import { API_BASE as CONFIG_API_BASE } from './config/apiConfig'; import { logger } from './debugConfig'; // Use development backend URL if no custom URL is provided -const baseURL = import.meta.env.VITE_API_BASE || 'http://localhost:8080'; +const baseURL = CONFIG_API_BASE; -if (!import.meta.env.VITE_API_BASE) { - logger.warn('axios', '⚠️ VITE_API_BASE not set, defaulting to http://localhost:8080'); +if (!import.meta.env.VITE_API_BASE && !import.meta.env.VITE_API_URL) { + logger.warn('axios', '⚠️ VITE_API_BASE not set, using relative /api fallback'); } const instance = axios.create({ diff --git a/src/config/apiConfig.ts b/src/config/apiConfig.ts new file mode 100644 index 0000000..4916252 --- /dev/null +++ b/src/config/apiConfig.ts @@ -0,0 +1,44 @@ +/** + * Centralized API configuration for Classroom Copilot. + * + * Resolves the API base URL from environment variables with proper fallbacks. + * - In dev: VITE_API_BASE should always be set (e.g., http://192.168.0.64:18000) + * - In production: VITE_API_BASE should be set to the production API URL + * - Fallback: "/api" (relative path, works when API is served from same origin) + * + * IMPORTANT: Never hardcode localhost or 127.0.0.1 as fallbacks - they break + * remote dev access and do not work in containerized environments. + */ + +/** + * Primary API base URL. Use this for all API calls. + * Resolved from VITE_API_BASE (preferred) or VITE_API_URL (legacy alias). + */ +export const API_BASE: string = + import.meta.env.VITE_API_BASE || + import.meta.env.VITE_API_URL || + "/api"; + +/** + * TLSync worker URL for tldraw multiplayer. + * Resolved from VITE_TLSYNC_URL (preferred) or derived from VITE_FRONTEND_SITE_URL. + */ +export const TLSYNC_URL: string = + import.meta.env.VITE_TLSYNC_URL || + (import.meta.env.VITE_FRONTEND_SITE_URL?.startsWith("http") + ? `${import.meta.env.VITE_FRONTEND_SITE_URL}/tldraw` + : `https://${import.meta.env.VITE_FRONTEND_SITE_URL}/tldraw`); + +/** + * WhisperLive WebSocket URL for live transcription. + * Resolved from VITE_WHISPERLIVE_URL. Must be set in env. + */ +export const WHISPERLIVE_URL: string | undefined = + import.meta.env.VITE_WHISPERLIVE_URL; + +/** + * Search proxy URL (searxng). + * Resolved from VITE_SEARCH_URL. + */ +export const SEARCH_URL: string = + import.meta.env.VITE_SEARCH_URL || "/searxng-api/"; diff --git a/src/pages/auth/PlatformAdminPage.tsx b/src/pages/auth/PlatformAdminPage.tsx index e973b00..89c5240 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; interface SchoolEntry { id: string; diff --git a/src/pages/dev/SimpleUploadTest.tsx b/src/pages/dev/SimpleUploadTest.tsx index 9729a34..034d1ad 100644 --- a/src/pages/dev/SimpleUploadTest.tsx +++ b/src/pages/dev/SimpleUploadTest.tsx @@ -123,7 +123,7 @@ const SimpleUploadTest: React.FC = () => { const fileInputRef = useRef(null); const dirInputRef = useRef(null); - const API_BASE = import.meta.env.VITE_API_BASE || 'http://127.0.0.1:8080'; + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; const apiFetch = useCallback(async (url: string, init?: { method?: string; body?: FormData | string; headers?: Record }) => { const session = await supabase.auth.getSession(); diff --git a/src/pages/timetable/ClassDetailPage.tsx b/src/pages/timetable/ClassDetailPage.tsx index 1eb2c1b..399125b 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/LessonPlanDetailPage.tsx b/src/pages/timetable/LessonPlanDetailPage.tsx index 6f8fd3d..27da778 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/LessonPlansPage.tsx b/src/pages/timetable/LessonPlansPage.tsx index 5855eea..e0e3a74 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/SchoolSettingsPage.tsx b/src/pages/timetable/SchoolSettingsPage.tsx index a62585d..03bc93f 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/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..24467e6 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/StudentLessonsPage.tsx b/src/pages/timetable/StudentLessonsPage.tsx index c21e3e7..789f706 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/timetable/StudentManagerPage.tsx b/src/pages/timetable/StudentManagerPage.tsx index 202e5c5..e1031ad 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; interface Student { profile_id: string; diff --git a/src/pages/timetable/TaughtLessonsPage.tsx b/src/pages/timetable/TaughtLessonsPage.tsx index ffccb49..f4ee3c7 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'; +const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx b/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx index b851d94..a098955 100644 --- a/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx +++ b/src/pages/tldraw/CCDocumentIntelligence/CCBundleViewer.tsx @@ -50,8 +50,8 @@ export const CCBundleViewer: React.FC<{ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'), []); - const API_BASE_FALLBACK = 'http://127.0.0.1:8080'; + const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || '/api', []); + const API_BASE_FALLBACK = '/api'; const proxyUrl = useCallback(async (bucket: string, relPath: string) => { const token = accessToken || ''; diff --git a/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx b/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx index fa03301..fec71ba 100644 --- a/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx +++ b/src/pages/tldraw/CCDocumentIntelligence/CCDoclingViewer.tsx @@ -182,7 +182,7 @@ export const CCDoclingViewer: React.FC<{ setError(null); try { // Try page-images manifest first - const API_BASE = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'); + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; try { const mRes = await fetch(`${API_BASE}/database/files/${encodeURIComponent(fileId)}/page-images/manifest`, { headers: { 'Authorization': `Bearer ${accessToken || ''}` } @@ -199,7 +199,7 @@ export const CCDoclingViewer: React.FC<{ } // Legacy: Load artefacts for file to find docling JSON artefacts - const artefactsRes = await fetch(`${import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api')}/database/files/${encodeURIComponent(fileId)}/artefacts`, { + const artefactsRes = await fetch(`${import.meta.env.VITE_API_BASE || '/api'}/database/files/${encodeURIComponent(fileId)}/artefacts`, { headers: { 'Authorization': `Bearer ${accessToken || ''}` } }); if (!artefactsRes.ok) throw new Error(await artefactsRes.text()); @@ -239,7 +239,7 @@ export const CCDoclingViewer: React.FC<{ run(); }, [fileId]); /* eslint-disable-line react-hooks/exhaustive-deps */ - const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'), []); + const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || '/api', []); const pageProxyUrl = useMemo(() => { if (!manifest) return undefined; diff --git a/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx b/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx index fd0f1f0..0ecbe6e 100644 --- a/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx +++ b/src/pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence.tsx @@ -148,7 +148,7 @@ export const CCDocumentIntelligence: React.FC = () => { useEffect(() => { const loadBundles = async () => { if (!validFileId) return; - const API_BASE = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'); + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; const token = accessToken || ''; const res = await fetch(`${API_BASE}/database/files/${encodeURIComponent(validFileId)}/artefacts`, { headers: { Authorization: `Bearer ${token}` } }); if (!res.ok) return; @@ -223,7 +223,7 @@ export const CCDocumentIntelligence: React.FC = () => { const run = async () => { if (!validFileId) return; setOutlineOptions([]); - const API_BASE = import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'); + const API_BASE = import.meta.env.VITE_API_BASE || '/api'; try { const artsRes = await fetch(`${API_BASE}/database/files/${encodeURIComponent(validFileId)}/artefacts`, { headers: { 'Authorization': `Bearer ${accessToken || ''}` } @@ -486,7 +486,7 @@ export const CCDocumentIntelligence: React.FC = () => {