diff --git a/backend/routers/detection.py b/backend/routers/detection.py index 558b8b5f..0181d26d 100644 --- a/backend/routers/detection.py +++ b/backend/routers/detection.py @@ -459,7 +459,7 @@ async def detect_abandoned_vehicle_endpoint(image: UploadFile = File(...)): @router.post("/api/detect-emotion") async def detect_emotion_endpoint( image: UploadFile = File(...), - client: httpx.AsyncClient = backend.dependencies.Depends(get_http_client) + client = backend.dependencies.Depends(get_http_client) ): """ Analyze facial emotions in the image using Hugging Face inference. diff --git a/frontend/babel.config.js b/frontend/babel.config.js index d8da6c80..daa278dd 100644 --- a/frontend/babel.config.js +++ b/frontend/babel.config.js @@ -2,5 +2,8 @@ export default { presets: [ ['@babel/preset-env', { targets: { node: 'current' } }], ['@babel/preset-react', { runtime: 'automatic' }] + ], + plugins: [ + 'babel-plugin-transform-vite-meta-env' ] }; \ No newline at end of file diff --git a/frontend/netlify.toml b/frontend/netlify.toml new file mode 100644 index 00000000..22956c24 --- /dev/null +++ b/frontend/netlify.toml @@ -0,0 +1,13 @@ +# Netlify Configuration for VishwaGuru Frontend + +[build] + # Execute from frontend dir + publish = "dist" + command = "npm install && npm run build" + +[build.environment] + NODE_VERSION = "20" + CI = "false" + +# Environment variables (set these in Netlify dashboard) +# VITE_API_URL = https://your-backend.onrender.com diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0b636255..06b9bc92 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -37,6 +37,8 @@ "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "babel-jest": "^29.7.0", + "babel-plugin-transform-import-meta": "^2.3.3", + "babel-plugin-transform-vite-meta-env": "^1.0.3", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", @@ -3969,6 +3971,31 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-transform-import-meta": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-import-meta/-/babel-plugin-transform-import-meta-2.3.3.tgz", + "integrity": "sha512-bbh30qz1m6ZU1ybJoNOhA2zaDvmeXMnGNBMVMDOJ1Fni4+wMBoy/j7MTRVmqAUCIcy54/rEnr9VEBsfcgbpm3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/template": "^7.25.9", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@babel/core": "^7.10.0" + } + }, + "node_modules/babel-plugin-transform-vite-meta-env": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-vite-meta-env/-/babel-plugin-transform-vite-meta-env-1.0.3.tgz", + "integrity": "sha512-eyfuDEXrMu667TQpmctHeTlJrZA6jXYHyEJFjcM0yEa60LS/LXlOg2PBbMb8DVS+V9CnTj/j9itdlDVMcY2zEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.9", + "@types/babel__core": "^7.1.12" + } + }, "node_modules/babel-preset-current-node-syntax": { "version": "1.2.0", "dev": true, diff --git a/frontend/package.json b/frontend/package.json index cec8f4f9..7fbda971 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,19 +17,19 @@ }, "dependencies": { "@supabase/supabase-js": "^2.95.3", + "@vitejs/plugin-react": "^5.1.2", + "autoprefixer": "^10.4.23", "dexie": "^4.2.1", "framer-motion": "^12.29.2", "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", "lucide-react": "^0.562.0", + "postcss": "^8.5.6", "react": "^19.2.0", "react-dom": "^19.2.0", "react-i18next": "^16.5.3", "react-router-dom": "^7.12.0", "react-webcam": "^7.2.0", - "@vitejs/plugin-react": "^5.1.2", - "autoprefixer": "^10.4.23", - "postcss": "^8.5.6", "tailwindcss": "^3.4.1", "vite": "^7.3.0", "vite-plugin-pwa": "^1.2.0" @@ -45,6 +45,8 @@ "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "babel-jest": "^29.7.0", + "babel-plugin-transform-import-meta": "^2.3.3", + "babel-plugin-transform-vite-meta-env": "^1.0.3", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", diff --git a/frontend/public/_headers b/frontend/public/_headers new file mode 100644 index 00000000..ca29b9d6 --- /dev/null +++ b/frontend/public/_headers @@ -0,0 +1,5 @@ +/* + X-Frame-Options: DENY + X-Content-Type-Options: nosniff + X-XSS-Protection: 1; mode=block + Referrer-Policy: strict-origin-when-cross-origin diff --git a/frontend/public/_redirects b/frontend/public/_redirects new file mode 100644 index 00000000..ad37e2c2 --- /dev/null +++ b/frontend/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/frontend/src/api/__tests__/detectors.test.js b/frontend/src/api/__tests__/detectors.test.js index 4e13022b..76453a7d 100644 --- a/frontend/src/api/__tests__/detectors.test.js +++ b/frontend/src/api/__tests__/detectors.test.js @@ -16,18 +16,18 @@ describe('detectorsApi', () => { }); const detectorTestCases = [ - { name: 'pothole', endpoint: '/api/detect-pothole' }, - { name: 'garbage', endpoint: '/api/detect-garbage' }, - { name: 'vandalism', endpoint: '/api/detect-vandalism' }, - { name: 'flooding', endpoint: '/api/detect-flooding' }, - { name: 'infrastructure', endpoint: '/api/detect-infrastructure' }, - { name: 'illegalParking', endpoint: '/api/detect-illegal-parking' }, - { name: 'streetLight', endpoint: '/api/detect-street-light' }, - { name: 'fire', endpoint: '/api/detect-fire' }, - { name: 'strayAnimal', endpoint: '/api/detect-stray-animal' }, - { name: 'blockedRoad', endpoint: '/api/detect-blocked-road' }, - { name: 'treeHazard', endpoint: '/api/detect-tree-hazard' }, - { name: 'pest', endpoint: '/api/detect-pest' } + { name: 'pothole', endpoint: '/detect-pothole' }, + { name: 'garbage', endpoint: '/detect-garbage' }, + { name: 'vandalism', endpoint: '/detect-vandalism' }, + { name: 'flooding', endpoint: '/detect-flooding' }, + { name: 'infrastructure', endpoint: '/detect-infrastructure' }, + { name: 'illegalParking', endpoint: '/detect-illegal-parking' }, + { name: 'streetLight', endpoint: '/detect-street-light' }, + { name: 'fire', endpoint: '/detect-fire' }, + { name: 'strayAnimal', endpoint: '/detect-stray-animal' }, + { name: 'blockedRoad', endpoint: '/detect-blocked-road' }, + { name: 'treeHazard', endpoint: '/detect-tree-hazard' }, + { name: 'pest', endpoint: '/detect-pest' } ]; detectorTestCases.forEach(({ name, endpoint }) => { @@ -114,7 +114,7 @@ describe('detectorsApi', () => { await detectorsApi.pothole(mockFormData); - expect(apiClient.postForm).toHaveBeenCalledWith('/api/detect-pothole', mockFormData); + expect(apiClient.postForm).toHaveBeenCalledWith('/detect-pothole', mockFormData); } }); diff --git a/frontend/src/api/__tests__/index.test.js b/frontend/src/api/__tests__/index.test.js index bac562cf..5fac513b 100644 --- a/frontend/src/api/__tests__/index.test.js +++ b/frontend/src/api/__tests__/index.test.js @@ -87,11 +87,15 @@ describe('API Index Exports', () => { // issues: issuesApi (1) // detectors: detectorsApi (1) // misc: miscApi (1) - // Total: 5 top-level exports + // auth: authApi (1) + // admin: adminApi (1) + // grievances: grievancesApi (1) + // resolutionProof: resolutionProofApi (1) + // Total: 9 top-level exports const exportKeys = Object.keys(api); - expect(exportKeys.length).toBe(5); + expect(exportKeys.length).toBe(9); - const expectedKeys = ['apiClient', 'getApiUrl', 'issuesApi', 'detectorsApi', 'miscApi']; + const expectedKeys = ['apiClient', 'getApiUrl', 'issuesApi', 'detectorsApi', 'miscApi', 'authApi', 'adminApi', 'grievancesApi', 'resolutionProofApi']; expectedKeys.forEach(key => { expect(exportKeys).toContain(key); }); diff --git a/frontend/src/api/__tests__/issues.test.js b/frontend/src/api/__tests__/issues.test.js index 36fb22bf..74ee2dbc 100644 --- a/frontend/src/api/__tests__/issues.test.js +++ b/frontend/src/api/__tests__/issues.test.js @@ -36,7 +36,7 @@ describe('issuesApi', () => { const result = await issuesApi.getRecent(); - expect(apiClient.get).toHaveBeenCalledWith('/api/issues/recent'); + expect(apiClient.get).toHaveBeenCalledWith('/issues/recent', { params: { limit: 10, offset: 0 } }); expect(result).toEqual(mockIssues); }); @@ -48,7 +48,7 @@ describe('issuesApi', () => { const result = await issuesApi.getRecent(); - expect(apiClient.get).toHaveBeenCalledWith('/api/issues/recent'); + expect(apiClient.get).toHaveBeenCalledWith('/issues/recent', { params: { limit: 10, offset: 0 } }); expect(result).toEqual(fakeRecentIssues); expect(consoleWarnSpy).toHaveBeenCalledWith('Failed to fetch recent issues, using fake data', error); @@ -81,7 +81,7 @@ describe('issuesApi', () => { const result = await issuesApi.create(mockFormData); - expect(apiClient.postForm).toHaveBeenCalledWith('/api/issues', mockFormData); + expect(apiClient.postForm).toHaveBeenCalledWith('/issues', mockFormData); expect(result).toEqual(mockResponse); }); @@ -106,7 +106,7 @@ describe('issuesApi', () => { const result = await issuesApi.create(mockFormData); - expect(apiClient.postForm).toHaveBeenCalledWith('/api/issues', mockFormData); + expect(apiClient.postForm).toHaveBeenCalledWith('/issues', mockFormData); expect(result).toEqual(mockResponse); }); }); @@ -120,7 +120,7 @@ describe('issuesApi', () => { const result = await issuesApi.vote(issueId); - expect(apiClient.post).toHaveBeenCalledWith('/api/issues/123/vote', {}); + expect(apiClient.post).toHaveBeenCalledWith('/issues/123/vote', {}); expect(result).toEqual(mockResponse); }); @@ -132,7 +132,7 @@ describe('issuesApi', () => { await issuesApi.vote(issueId); - expect(apiClient.post).toHaveBeenCalledWith(`/api/issues/${issueId}/vote`, {}); + expect(apiClient.post).toHaveBeenCalledWith(`/issues/${issueId}/vote`, {}); } }); diff --git a/frontend/src/api/__tests__/misc.test.js b/frontend/src/api/__tests__/misc.test.js index a7a69ca7..e9cb1240 100644 --- a/frontend/src/api/__tests__/misc.test.js +++ b/frontend/src/api/__tests__/misc.test.js @@ -35,7 +35,7 @@ describe('miscApi', () => { const result = await miscApi.getResponsibilityMap(); - expect(apiClient.get).toHaveBeenCalledWith('/api/responsibility-map'); + expect(apiClient.get).toHaveBeenCalledWith('/responsibility-map'); expect(result).toEqual(mockMap); }); @@ -47,7 +47,7 @@ describe('miscApi', () => { const result = await miscApi.getResponsibilityMap(); - expect(apiClient.get).toHaveBeenCalledWith('/api/responsibility-map'); + expect(apiClient.get).toHaveBeenCalledWith('/responsibility-map'); expect(result).toEqual(fakeResponsibilityMap); expect(consoleWarnSpy).toHaveBeenCalledWith('Failed to fetch responsibility map, using fake data', error); @@ -79,7 +79,7 @@ describe('miscApi', () => { const result = await miscApi.chat(message); - expect(apiClient.post).toHaveBeenCalledWith('/api/chat', { query: message }); + expect(apiClient.post).toHaveBeenCalledWith('/chat', { query: message }); expect(result).toEqual(mockResponse); }); @@ -96,7 +96,7 @@ describe('miscApi', () => { await miscApi.chat(message); - expect(apiClient.post).toHaveBeenCalledWith('/api/chat', { query: message }); + expect(apiClient.post).toHaveBeenCalledWith('/chat', { query: message }); } }); @@ -117,7 +117,7 @@ describe('miscApi', () => { const result = await miscApi.chat(message); - expect(apiClient.post).toHaveBeenCalledWith('/api/chat', { query: message }); + expect(apiClient.post).toHaveBeenCalledWith('/chat', { query: message }); expect(result).toEqual(mockResponse); }); }); @@ -135,7 +135,7 @@ describe('miscApi', () => { const result = await miscApi.getRepContact(pincode); - expect(apiClient.get).toHaveBeenCalledWith('/api/mh/rep-contacts?pincode=400001'); + expect(apiClient.get).toHaveBeenCalledWith('/mh/rep-contacts?pincode=400001'); expect(result).toEqual(mockResponse); }); @@ -147,7 +147,7 @@ describe('miscApi', () => { await miscApi.getRepContact(pincode); - expect(apiClient.get).toHaveBeenCalledWith(`/api/mh/rep-contacts?pincode=${pincode}`); + expect(apiClient.get).toHaveBeenCalledWith(`/mh/rep-contacts?pincode=${pincode}`); } }); @@ -189,7 +189,7 @@ describe('miscApi', () => { const result = await miscApi.getStats(); - expect(apiClient.get).toHaveBeenCalledWith('/api/stats'); + expect(apiClient.get).toHaveBeenCalledWith('/stats'); expect(result).toEqual(mockStats); }); diff --git a/netlify.toml b/netlify.toml index 66890e7a..3ef693f0 100644 --- a/netlify.toml +++ b/netlify.toml @@ -12,17 +12,3 @@ # Environment variables (set these in Netlify dashboard) # VITE_API_URL = https://your-backend.onrender.com -# Redirects for SPA -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 - -# Headers for security -[[headers]] - for = "/*" - [headers.values] - X-Frame-Options = "DENY" - X-Content-Type-Options = "nosniff" - X-XSS-Protection = "1; mode=block" - Referrer-Policy = "strict-origin-when-cross-origin"