diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..6718841 --- /dev/null +++ b/.env.development @@ -0,0 +1,18 @@ +PORT_FRONTEND=5173 +PORT_FRONTEND_HMR=3002 +PORT_API=800 +PORT_SUPABASE=8000 + +HOST_FRONTEND=localhost:5173 +VITE_PORT_FRONTEND=5173 +VITE_PORT_FRONTEND_HMR=5173 + +VITE_APP_NAME=Classroom Copilot +VITE_SUPER_ADMIN_EMAIL=admin@classroomcopilot.ai +VITE_DEV=true +VITE_FRONTEND_SITE_URL=http://localhost:5173 +VITE_APP_HMR_URL=http://localhost:5173 +VITE_SUPABASE_URL=http://localhost:8000 +VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiaWF0IjoxNzcxNzAwMTAwLCJpc3MiOiJzdXBhYmFzZSIsInN1YiI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsImV4cCI6MjA4NzA2MDEwMCwicm9sZSI6ImFub24ifQ.-ZIBd7I6DeBgIlj_JJMvrvPqvdrQAMDuOvp-zddDsmc +VITE_API_URL=http://localhost:8080 +VITE_API_BASE=http://localhost:8080 \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 6368c1f..833a1ed 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,177 +1,122 @@ -/// -/// +import { defineConfig, loadEnv } from 'vite' +import react from '@vitejs/plugin-react' +import { VitePWA } from 'vite-plugin-pwa' +import path from 'path' -import { defineConfig, loadEnv, UserConfig, ConfigEnv } from 'vite'; -import react from '@vitejs/plugin-react'; -import process from 'node:process'; -import { VitePWA } from 'vite-plugin-pwa'; +// https://vitejs.dev/config/ +export default defineConfig(({ mode }) => { + // Load env file based on mode (development, production, etc.) + // This loads .env.development for dev mode + const env = loadEnv(mode, process.cwd(), '') -export default defineConfig(async ({ mode }: ConfigEnv): Promise => { - // Load env file based on mode in correct order - const env = loadEnv(mode, process.cwd(), 'VITE_'); + // Check if we're behind an SSL proxy + const isSSLProxy = env.VITE_FRONTEND_SITE_URL?.startsWith('https://') - // Determine base URL from hostname - const base = '/'; // Always use root path, let nginx handle the routing - - // Determine if we're in production based on mode and VITE_DEV flag - const isProd = mode === 'production' && env.VITE_DEV !== 'true'; + // Determine client-side env vars to expose + const envPrefix = 'VITE_' + const clientEnv = Object.fromEntries( + Object.entries(env) + .filter(([key]) => key.startsWith(envPrefix)) + .map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)]) + ) return { plugins: [ react(), VitePWA({ + registerType: 'autoUpdate', + injectRegister: 'inline', strategies: 'injectManifest', srcDir: 'src', filename: 'sw.ts', - registerType: 'prompt', - injectRegister: 'auto', devOptions: { - enabled: false, - type: 'module' + enabled: true, + type: 'module', }, manifest: { - name: 'ClassroomCopilot', - short_name: 'CC', - start_url: base, - scope: base, - display: 'fullscreen', + name: 'Classroom Copilot', + short_name: 'Classroom Copilot', + description: 'AI-powered teaching assistant', + theme_color: '#ffffff', background_color: '#ffffff', - theme_color: '#000000', + display: 'standalone', + scope: '/', + start_url: '/', icons: [ { src: '/icons/icon-192x192.png', sizes: '192x192', type: 'image/png', - purpose: 'any' }, { src: '/icons/icon-512x512.png', sizes: '512x512', type: 'image/png', - purpose: 'any' }, { src: '/icons/icon-192x192-maskable.png', sizes: '192x192', type: 'image/png', - purpose: 'maskable' + purpose: 'maskable', }, { src: '/icons/icon-512x512-maskable.png', sizes: '512x512', type: 'image/png', - purpose: 'maskable' - } - ] + purpose: 'maskable', + }, + ], }, - includeAssets: ['favicon.ico', 'icons/*.png'], - injectManifest: { - globPatterns: [ - 'index.html', - '**/*.{js,css,html,ico,png,svg,json}', - 'manifest.webmanifest' - ], - maximumFileSizeToCacheInBytes: 8 * 1024 * 1024, // 8MB - dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, - // Exclude development resources and source files - globIgnores: [ - '**/node_modules/**/*', - 'sw.js', - 'workbox-*.js', - '**/*.map', - '**/vite/**/*', - '**/@vite/**/*', - '**/@react-refresh/**/*' - ], - // Ensure service worker has correct scope - swDest: 'dist/sw.js', - manifestTransforms: [ - // Transform manifest entries to ensure proper caching - (entries: any[]) => ({ - manifest: entries.map(entry => ({ - ...entry, - url: entry.url.startsWith(base) ? entry.url : `${base}${entry.url.startsWith('/') ? entry.url.slice(1) : entry.url}` - })) - }) - ] - } - }) + workbox: { + globPatterns: ['**/*.{js,css,html,ico,png,svg,json,vue,txt,woff2}'], + cleanupOutdatedCaches: true, + clientsClaim: true, + skipWaiting: true, + }, + }), ], + + // Define client-side env vars define: { - // Make env variables available globally - 'process.env': env, - // Ensure import.meta.env has our variables - 'import.meta.env.VITE_FRONTEND_SITE_URL': JSON.stringify(env.VITE_FRONTEND_SITE_URL), - 'import.meta.env.VITE_SUPABASE_URL': JSON.stringify(env.VITE_SUPABASE_URL), - 'import.meta.env.VITE_SUPABASE_ANON_KEY': JSON.stringify(env.VITE_SUPABASE_ANON_KEY), - 'import.meta.env.VITE_SUPER_ADMIN_EMAIL': JSON.stringify(env.VITE_SUPER_ADMIN_EMAIL), - 'import.meta.env.VITE_DEV': env.VITE_DEV === 'true', - 'import.meta.env.VITE_API_BASE': JSON.stringify(env.VITE_API_BASE), - 'import.meta.env.VITE_SEARCH_URL': JSON.stringify(env.VITE_SEARCH_URL), + ...clientEnv, + // Explicitly define VITE_DEV for runtime checks + 'import.meta.env.VITE_DEV': JSON.stringify(env.VITE_DEV || 'false'), + 'import.meta.env.DEV': mode === 'development', + 'import.meta.env.PROD': mode === 'production', }, - envPrefix: 'VITE_', - base, + server: { host: '0.0.0.0', port: parseInt(env.VITE_PORT_FRONTEND || '5173'), - watch: { - usePolling: env.VITE_DEV === 'true', - ignored: ['**/node_modules/**', '**/dist/**'] + strictPort: true, + + // HMR configuration for SSL proxy + hmr: isSSLProxy ? { + // When behind SSL proxy, use WSS and public host + protocol: 'wss', + host: '192.168.0.94', + port: 5173, + clientPort: 5173, + } : { + // Direct HTTP development + protocol: 'ws', + host: 'localhost', + port: parseInt(env.VITE_PORT_FRONTEND_HMR || '5173'), }, - allowedHosts: [ - `app.${env.VITE_FRONTEND_SITE_URL}`, - ], - hmr: isProd ? false : { - protocol: env.VITE_DEV === 'true' ? 'ws' : 'wss', - host: env.VITE_DEV == 'true' ? 'localhost' : env.VITE_APP_HMR_URL, - port: parseInt(env.VITE_PORT_FRONTEND || '5173'), - clientPort: parseInt(env.VITE_PORT_FRONTEND_HMR || '5173'), - overlay: false - }, - proxy: { - '/searxng-api': { - target: env.VITE_SEARCH_URL, - changeOrigin: true, - rewrite: (path: string) => path.replace(/^\/searxng-api/, '') - } - } - }, - clearScreen: false, - optimizeDeps: { - force: true, - include: ['react', 'react-dom', '@mui/material', '@tldraw/tldraw'] + + // Allow all origins for development + cors: true, }, + build: { - sourcemap: !isProd, - manifest: true, - minify: isProd ? 'terser' : false, - terserOptions: isProd ? { - compress: { - drop_debugger: true - } - } : undefined, - rollupOptions: { - output: { - // Ensure chunk filenames include content hash - chunkFileNames: isProd ? 'assets/[name].[hash].js' : 'assets/[name].js', - assetFileNames: isProd ? 'assets/[name].[hash][extname]' : 'assets/[name][extname]' - }, - // Externalize dependencies that shouldn't be bundled - external: isProd ? [] : [/^@vite/, /^@react-refresh/] - }, - chunkSizeWarningLimit: 2000, - // Enable module concatenation for better minification - target: 'esnext', - cssCodeSplit: true, - assetsInlineLimit: 4096, // 4kb - modulePreload: true, - reportCompressedSize: !isProd + outDir: 'dist', + sourcemap: mode === 'development', }, - // Add esbuild optimization - esbuild: { - drop: isProd ? ['debugger'] : [], - legalComments: 'none', - target: ['esnext'] - } - }; -}); + + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + } +})