diff --git a/index.html b/index.html index b275768..54e35db 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,13 @@
+ + diff --git a/nginx.conf b/nginx.conf index da0ee43..58bde36 100644 --- a/nginx.conf +++ b/nginx.conf @@ -19,6 +19,13 @@ server { add_header Cache-Control "no-cache, no-store, must-revalidate"; } + # Runtime config — never cached. It is how the API base is changed without a + # rebuild, and a cached copy would silently pin the old URL for every browser + # that has already loaded the app, with no way to tell from the server side. + location = /config.js { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + # The API, same-origin. The bundle is built with VITE_UDYAM_API_BASE_URL set # to the relative /api/v1/udyam, so it carries no hostname at all and the # same dist/ serves from any domain. That also puts the API on the UI's own diff --git a/public/config.js b/public/config.js new file mode 100644 index 0000000..63c73dc --- /dev/null +++ b/public/config.js @@ -0,0 +1,30 @@ +/* + * Runtime configuration — edited on the server, not baked into the bundle. + * + * Vite inlines import.meta.env.VITE_* at build time, so a bundle built for one + * backend URL cannot be pointed at another without rebuilding. That is the wrong + * shape for a deployment where the backend goes up first and the UI follows, or + * where the same artifact is promoted from demo to staging. This file is served + * from the static root unbundled, so ops can change the API base and reload. + * + * Precedence, highest first: + * 1. apiBaseUrl below, if it is a non-empty string + * 2. VITE_UDYAM_API_BASE_URL, compiled in at build time + * 3. "/api/v1/udyam" — same-origin, which is what the nginx and httpd configs + * in DEPLOYMENT.md set up + * + * Leave it empty for a same-origin deployment; that is the common case and needs + * no edit. Set it when the API lives on another host: + * + * apiBaseUrl: "https://udyamlinc-api.lincpay.tech/api/v1/udyam" + * + * A cross-origin value also needs that origin in the backend's + * UDYAM_CORS_ALLOWED_ORIGINS, or the browser blocks every call while the API + * itself looks healthy. + * + * This file must never be cached — the server configs send no-store for it. + * Cached, it silently pins the old URL for anyone who has already loaded the app. + */ +window.__UDYAM_CONFIG__ = { + apiBaseUrl: '', +} diff --git a/src/lib/api.ts b/src/lib/api.ts index 421896c..616ec3d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,8 +1,43 @@ import axios from 'axios' -/** udyamlinc-backend — Spring Boot skeleton, context path /api/v1/udyam. */ -export const API_BASE = - import.meta.env.VITE_UDYAM_API_BASE_URL ?? 'http://udyamlinc-backend.lincpay/api/v1/udyam' +declare global { + interface Window { + __UDYAM_CONFIG__?: { apiBaseUrl?: string } + } +} + +/** + * Where the API lives — resolved at runtime, not frozen into the bundle. + * + *

Precedence, highest first: + * + * 1. `window.__UDYAM_CONFIG__.apiBaseUrl` from `public/config.js`, which is served + * unbundled and can be edited on the server. This is what lets the backend be + * deployed first and the UI pointed at it afterwards, without a rebuild. + * 2. `VITE_UDYAM_API_BASE_URL`, inlined at build time — still useful for `npm run + * dev` against a local backend. + * 3. `/api/v1/udyam` — relative, so the bundle carries no hostname, works from any + * domain, and is same-origin with the API that nginx or httpd proxies. Being + * same-origin also means CORS never applies, which removes the largest single + * source of works-locally-fails-deployed. + * + *

An empty or whitespace-only value counts as unset. A deployment that writes + * `apiBaseUrl: ""` into config.js means "use the default", and treating that as a + * real base would send every request to the page's own URL. + */ +function resolveApiBase(): string { + const runtime = window.__UDYAM_CONFIG__?.apiBaseUrl + if (typeof runtime === 'string' && runtime.trim() !== '') { + return runtime.trim().replace(/\/+$/, '') + } + const compiled = import.meta.env.VITE_UDYAM_API_BASE_URL + if (typeof compiled === 'string' && compiled.trim() !== '') { + return compiled.trim().replace(/\/+$/, '') + } + return '/api/v1/udyam' +} + +export const API_BASE = resolveApiBase() /** Short timeout: field devices are on patchy 2G and every screen has a local fallback. */ export const api = axios.create({ baseURL: API_BASE, timeout: 6_000 })