diff --git a/backend/auth.py b/backend/auth.py index 9b5e64d..f13cabb 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -1,4 +1,54 @@ +# ────────────────────────────────────────────── +# +# ────────────────────────────────────────────── + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from supabase import create_client import os + +# Create a router for auth endpoints +router = APIRouter(prefix="/api/v1/auth", tags=["auth"]) + +# Request body model — expects a refresh_token string +class RefreshRequest(BaseModel): + refresh_token: str + +@router.post("/refresh") +async def refresh_session(body: RefreshRequest): + """ + Exchange a Supabase refresh_token for a new access_token + refresh_token. + Called automatically by the frontend when a 401 Unauthorized error occurs. + """ + try: + # Create a Supabase client using environment variables + supabase_url = os.environ.get("SUPABASE_URL") + supabase_key = os.environ.get("SUPABASE_KEY") + + if not supabase_url or not supabase_key: + raise HTTPException(status_code=500, detail="Supabase environment variables not configured.") + + supabase = create_client(supabase_url, supabase_key) + + # Ask Supabase to give us a fresh session using the refresh token + response = supabase.auth.refresh_session(body.refresh_token) + + # If Supabase didn't return a session, something is wrong + if not response or not response.session: + raise HTTPException(status_code=401, detail="Invalid or expired refresh token") + + # Return the new tokens to the frontend + return { + "access_token": response.session.access_token, + "refresh_token": response.session.refresh_token, + } + + except HTTPException: + # Re-raise HTTP exceptions as-is + raise + except Exception as e: + # Any other error means the refresh failed + raise HTTPException(status_code=401, detail=f"Token refresh failed: {str(e)}") import uuid from fastapi import HTTPException, Header from supabase import create_client, Client diff --git a/backend/main.py b/backend/main.py index ab578ce..d59f4ce 100644 --- a/backend/main.py +++ b/backend/main.py @@ -6,6 +6,7 @@ from datetime import datetime, timezone from contextlib import asynccontextmanager from typing import Optional +from auth import router as auth_router from auth import get_current_user, get_google_oauth_url, exchange_code_for_session from slowapi.errors import RateLimitExceeded from slowapi.middleware import SlowAPIMiddleware @@ -95,7 +96,8 @@ async def lifespan(app: FastAPI): from fastapi import FastAPI -app = FastAPI(title="FreshScan AI", version="1.1.0", lifespan=lifespan) +app = FastAPI(title="FreshScan AI", version="1.1.0", lifespan=lifespan) +app.include_router(auth_router) _cors_origins = ( ["*"] diff --git a/package-lock.json b/package-lock.json index d6e7bfc..0e1ea83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,11 +27,11 @@ "devDependencies": { "@eslint/js": "^9.39.4", "@types/leaflet": "^1.9.21", - "@types/node": "^25.9.1", + "@types/node": "^25.9.3", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", - "concurrently": "^9.2.1", + "@vitejs/plugin-react": "^6.0.2", + "concurrently": "^10.0.3", "cross-env": "^10.1.0", "eslint": "^9.39.4", "eslint-plugin-react-hooks": "^7.0.1", @@ -39,9 +39,13 @@ "globals": "^17.4.0", "typescript": "~6.0.3", "typescript-eslint": "^8.57.0", +<<<<<<< HEAD "vite": "^8.0.1", "vite-plugin-pwa": "^1.3.0", "workbox-window": "^7.4.1" +======= + "vite": "^8.0.16" +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) } }, "node_modules/@babel/code-frame": { @@ -1764,13 +1768,19 @@ } }, "node_modules/@napi-rs/wasm-runtime": { +<<<<<<< HEAD "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", +======= + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.2" }, "funding": { "type": "github", @@ -1996,7 +2006,13 @@ } }, "node_modules/@oxc-project/types": { +<<<<<<< HEAD "version": "0.122.0", +======= + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -2066,9 +2082,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -2082,7 +2098,13 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { +<<<<<<< HEAD "version": "1.0.0-rc.12", +======= + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "cpu": [ "arm64" ], @@ -2096,9 +2118,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -2112,9 +2134,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -2128,9 +2150,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -2144,9 +2166,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -2163,9 +2185,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -2182,9 +2204,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -2201,9 +2223,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -2220,9 +2242,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -2239,9 +2261,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -2258,9 +2280,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -2274,25 +2296,27 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -2306,9 +2330,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -2322,8 +2346,14 @@ } }, "node_modules/@rolldown/pluginutils": { +<<<<<<< HEAD "version": "1.0.0-rc.7", "dev": true, +======= + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT" }, "node_modules/@rollup/plugin-babel": { @@ -3132,7 +3162,13 @@ } }, "node_modules/@types/node": { +<<<<<<< HEAD "version": "25.9.1", +======= + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -3342,7 +3378,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { +<<<<<<< HEAD "version": "5.0.5", +======= + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "dependencies": { @@ -3427,11 +3469,17 @@ } }, "node_modules/@vitejs/plugin-react": { +<<<<<<< HEAD "version": "6.0.1", +======= + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.7" + "@rolldown/pluginutils": "^1.0.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -3485,11 +3533,20 @@ } }, "node_modules/ansi-regex": { +<<<<<<< HEAD "version": "5.0.1", +======= + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -3766,16 +3823,22 @@ } }, "node_modules/cliui": { +<<<<<<< HEAD "version": "8.0.1", +======= + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/color-convert": { @@ -3813,37 +3876,60 @@ "license": "MIT" }, "node_modules/concurrently": { +<<<<<<< HEAD "version": "9.2.1", +======= + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", + "integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "dependencies": { - "chalk": "4.1.2", + "chalk": "5.6.2", "rxjs": "7.8.2", - "shell-quote": "1.8.3", - "supports-color": "8.1.1", + "shell-quote": "1.8.4", + "supports-color": "10.2.2", "tree-kill": "1.2.2", - "yargs": "17.7.2" + "yargs": "18.0.0" }, "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" + "conc": "dist/bin/index.js", + "concurrently": "dist/bin/index.js" }, "engines": { - "node": ">=18" + "node": ">=22" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, +<<<<<<< HEAD "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", +======= + "node_modules/concurrently/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" @@ -4083,7 +4169,13 @@ "license": "ISC" }, "node_modules/emoji-regex": { +<<<<<<< HEAD "version": "8.0.0", +======= + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT" }, @@ -4682,6 +4774,7 @@ "node": "6.* || 8.* || >= 10.*" } }, +<<<<<<< HEAD "node_modules/get-intrinsic": { "version": "1.3.0", "dev": true, @@ -4758,6 +4851,19 @@ }, "funding": { "url": "https://github.com/sponsors/isaacs" +======= + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) } }, "node_modules/glob-parent": { @@ -5158,6 +5264,7 @@ "node": ">=0.10.0" } }, +<<<<<<< HEAD "node_modules/is-finalizationregistry": { "version": "1.1.1", "dev": true, @@ -5198,6 +5305,8 @@ "url": "https://github.com/sponsors/ljharb" } }, +======= +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "node_modules/is-glob": { "version": "4.0.3", "dev": true, @@ -5918,7 +6027,13 @@ "license": "MIT" }, "node_modules/nanoid": { +<<<<<<< HEAD "version": "3.3.11", +======= + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "funding": [ { "type": "github", @@ -6144,7 +6259,13 @@ } }, "node_modules/postcss": { +<<<<<<< HEAD "version": "8.5.8", +======= + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "funding": [ { "type": "opencollective", @@ -6161,7 +6282,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6351,7 +6472,13 @@ } }, "node_modules/react-router": { +<<<<<<< HEAD "version": "7.14.0", +======= + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz", + "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -6371,10 +6498,16 @@ } }, "node_modules/react-router-dom": { +<<<<<<< HEAD "version": "7.14.0", +======= + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz", + "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "dependencies": { - "react-router": "7.14.0" + "react-router": "7.17.0" }, "engines": { "node": ">=20.0.0" @@ -6384,6 +6517,7 @@ "react-dom": ">=18" } }, +<<<<<<< HEAD "node_modules/reflect.getprototypeof": { "version": "1.0.10", "dev": true, @@ -6508,6 +6642,8 @@ "url": "https://github.com/sponsors/ljharb" } }, +======= +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "node_modules/resolve-from": { "version": "4.0.0", "dev": true, @@ -6517,11 +6653,17 @@ } }, "node_modules/rolldown": { +<<<<<<< HEAD "version": "1.0.0-rc.12", +======= + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -6530,23 +6672,24 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" - } - }, + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, +<<<<<<< HEAD "node_modules/rolldown/node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.12", "license": "MIT" @@ -6594,6 +6737,8 @@ "fsevents": "~2.3.2" } }, +======= +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "node_modules/rxjs": { "version": "7.8.2", "dev": true, @@ -6750,7 +6895,13 @@ } }, "node_modules/shell-quote": { +<<<<<<< HEAD "version": "1.8.3", +======= + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "engines": { @@ -6895,16 +7046,25 @@ } }, "node_modules/string-width": { +<<<<<<< HEAD "version": "4.2.3", +======= + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string.prototype.matchall": { @@ -7001,14 +7161,23 @@ } }, "node_modules/strip-ansi": { +<<<<<<< HEAD "version": "6.0.1", +======= + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-comments": { @@ -7110,11 +7279,17 @@ } }, "node_modules/tinyglobby": { +<<<<<<< HEAD "version": "0.2.15", +======= + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -7410,14 +7585,20 @@ } }, "node_modules/vite": { +<<<<<<< HEAD "version": "8.0.3", +======= + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", - "tinyglobby": "^0.2.15" + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -7433,8 +7614,8 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", @@ -7864,21 +8045,40 @@ } }, "node_modules/wrap-ansi": { +<<<<<<< HEAD "version": "7.0.0", +======= + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/y18n": { "version": "5.0.8", "dev": true, @@ -7893,28 +8093,39 @@ "license": "ISC" }, "node_modules/yargs": { +<<<<<<< HEAD "version": "17.7.2", +======= + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { +<<<<<<< HEAD "version": "21.1.1", +======= + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) "dev": true, "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { diff --git a/package.json b/package.json index 1525380..d185add 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,11 @@ "devDependencies": { "@eslint/js": "^9.39.4", "@types/leaflet": "^1.9.21", - "@types/node": "^25.9.1", + "@types/node": "^25.9.3", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", - "concurrently": "^9.2.1", + "@vitejs/plugin-react": "^6.0.2", + "concurrently": "^10.0.3", "cross-env": "^10.1.0", "eslint": "^9.39.4", "eslint-plugin-react-hooks": "^7.0.1", @@ -47,8 +47,12 @@ "globals": "^17.4.0", "typescript": "~6.0.3", "typescript-eslint": "^8.57.0", +<<<<<<< HEAD "vite": "^8.0.1", "vite-plugin-pwa": "^1.3.0", "workbox-window": "^7.4.1" +======= + "vite": "^8.0.16" +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) } } diff --git a/src/lib/api.ts b/src/lib/api.ts index a77e1cb..90d8ffa 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -7,12 +7,11 @@ import type { UserProfile, } from './types'; -// Base URL — override with VITE_API_URL in .env for production -const API_BASE = (import.meta.env.VITE_API_URL as string | undefined) || 'http://localhost:8000'; - -// ── Token management ────────────────────────────────────────────────────────── +// Base URL +const API_BASE: string = window.__VITE_API_URL__ || 'http://localhost:8000'; const TOKEN_KEY = 'fs_access_token'; +const REFRESH_KEY = 'fs_refresh_token'; export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY); @@ -25,6 +24,7 @@ export function setToken(token: string): void { export function clearToken(): void { localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(REFRESH_KEY); window.dispatchEvent(new Event('auth-change')); } @@ -37,17 +37,53 @@ function authHeaders(): Record { return token ? { Authorization: `Bearer ${token}` } : {}; } -// ── Shared Error Handling Logic ────────────────────────────────────────────── +async function tryRefreshToken(): Promise { + const refreshToken = localStorage.getItem(REFRESH_KEY); + if (!refreshToken) return false; + try { + const res = await fetch(`${API_BASE}/api/v1/auth/refresh`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ refresh_token: refreshToken }), + }); + if (!res.ok) return false; + const data = await res.json(); + localStorage.setItem(TOKEN_KEY, data.access_token); + localStorage.setItem(REFRESH_KEY, data.refresh_token); + window.dispatchEvent(new Event('auth-change')); + return true; + } catch { + return false; + } +} +async function handleResponse(res: Response): Promise { + if (res.ok) return res; +<<<<<<< HEAD + + // Handle 401 — try to refresh token and retry once + if (res.status === 401 && !retried) { + const refreshed = await tryRefreshToken(); + if (!refreshed) { + clearToken(); + window.location.href = '/auth'; + throw new Error('Session expired. Redirecting to login.'); + } + // Return a special signal to retry + return res; + } + + // Handle 5xx errors (Server Side) async function handleResponse(res: Response): Promise { if (res.ok) return res; +======= +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) if (res.status >= 500) { - const msg = "Server error. Please try again later."; + const msg = 'Server error. Please try again later.'; toast.error(msg); throw new Error(msg); } - const err = await res.json().catch(() => ({ detail: res.statusText })); throw new Error((err as { detail?: string }).detail || `HTTP ${res.status}`); } @@ -55,12 +91,28 @@ async function handleResponse(res: Response): Promise { async function safeFetch(input: RequestInfo | URL, init?: RequestInit): Promise { try { const res = await fetch(input, init); + if (res.status === 401) { + const refreshed = await tryRefreshToken(); + if (!refreshed) { + clearToken(); + window.location.href = '/auth'; + throw new Error('Session expired.'); + } + const retryRes = await fetch(input, { + ...init, + headers: { + ...(init?.headers as Record || {}), + ...authHeaders(), + }, + }); + return await handleResponse(retryRes); + } return await handleResponse(res); } catch (error) { if (error instanceof TypeError) { - toast.error("Unable to connect to the server. Please check your internet connection."); + toast.error('Unable to connect to the server. Please check your internet connection.'); } - console.error("API Error:", error); + console.error('API Error:', error); throw error; } } @@ -77,13 +129,18 @@ async function apiFetch(path: string, options: RequestInit = {}): Promise return validRes.json() as Promise; } +<<<<<<< HEAD // ── Response envelopes ──────────────────────────────────────────────────────── export interface ScanResponse { success: boolean; scan: ScanResult; } +======= +export interface ScanResponse { success: boolean; scan: ScanResult; } +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) export interface HistoryResponse { success: boolean; count: number; stats: HistoryStats; scans: HistoryScan[]; } export interface MarketsResponse { success: boolean; markets: Market[]; } export interface GradcamResponse { gradcam_image: string; predicted_class: string; class_index: number; mode: 'real' | 'demo'; } +<<<<<<< HEAD // Metadata sent alongside edge-inference results so the backend can store them // without re-running the ML pipeline on the server. export interface EdgeInferenceMeta { @@ -94,10 +151,12 @@ export interface EdgeInferenceMeta { // ── API surface ─────────────────────────────────────────────────────────────── +======= +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) export const api = { loginUrl: (): string => `${API_BASE}/api/v1/auth/login/google`, - getMe: (): Promise => apiFetch('/api/v1/auth/me'), +<<<<<<< HEAD // ── Scans ──────────────────────────────────────────────────────────────── // meta is optional — when provided (edge inference path), the backend skips @@ -111,14 +170,19 @@ export const api = { if (meta?.fused_score !== undefined) form.append('fused_score', String(meta.fused_score)); if (meta?.source) form.append('source', meta.source); +======= + submitScan: async (blob: Blob): Promise => { + const form = new FormData(); + form.append('image', blob, 'scan.jpg'); +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) const validRes = await safeFetch(`${API_BASE}/api/v1/scan-auto`, { method: 'POST', headers: authHeaders(), body: form, }); - return validRes.json() as Promise; }, +<<<<<<< HEAD /** * Try the HF backend with a single image (same as submitScan with no meta). @@ -159,19 +223,32 @@ export const api = { apiFetch(`/api/v1/scans/history?limit=${limit}&offset=${offset}`), // ── Grad-CAM ───────────────────────────────────────────────────────────── +======= + getLatestScan: (): Promise => apiFetch('/api/v1/scans/latest'), + getScan: (id: string): Promise => apiFetch(`/api/v1/scans/${id}`), + getScanHistory: (limit = 20, offset = 0): Promise => + apiFetch(`/api/v1/scans/history?limit=${limit}&offset=${offset}`), +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) getGradcam: async (blob: Blob): Promise => { const form = new FormData(); form.append('image', blob, 'gradcam_input.jpg'); - const validRes = await safeFetch(`${API_BASE}/api/v1/gradcam`, { method: 'POST', headers: authHeaders(), body: form, }); - return validRes.json() as Promise; }, +<<<<<<< HEAD getMarkets: (): Promise => apiFetch('/api/v1/maps/markets'), }; +======= + getMarkets: (): Promise => apiFetch('/api/v1/maps/markets'), +}; + +declare global { + interface Window { __VITE_API_URL__?: string; } +} +>>>>>>> 8f270f1 (fix: resolve all TypeScript errors in api.ts) diff --git a/src/pages/AuthPage.tsx b/src/pages/AuthPage.tsx index 616e2ec..3874879 100644 --- a/src/pages/AuthPage.tsx +++ b/src/pages/AuthPage.tsx @@ -18,6 +18,7 @@ export default function AuthPage() { useEffect(() => { const params = new URLSearchParams(window.location.search); const accessToken = params.get('access_token'); + const refreshToken = params.get('refresh_token'); const error = params.get('error'); if (error) { @@ -30,6 +31,9 @@ export default function AuthPage() { if (accessToken) { setStatus('processing'); setToken(accessToken); + if (refreshToken) { + localStorage.setItem('fs_refresh_token', refreshToken); + } window.history.replaceState({}, '', '/auth'); navigate('/mode', { replace: true }); return;