Updated frontend

This commit is contained in:
sriram
2026-08-12 16:33:39 +05:30
parent d2ac832960
commit dae0b3e0fb
14 changed files with 297 additions and 164 deletions

View File

@@ -1,5 +1,49 @@
const BASE = import.meta.env.VITE_API_BASE_URL || '';
// Where the access token lives. sessionStorage, not localStorage: the token is
// a bearer credential, and a tab-scoped store means closing the tab ends the
// session rather than leaving a working credential on disk.
export const TOKEN_STORAGE_KEY = 'app_access_token';
// Read at module load so a page refresh is already authenticated before
// AuthContext mounts and the first request goes out.
let authToken = (() => {
try {
return sessionStorage.getItem(TOKEN_STORAGE_KEY);
} catch {
return null;
}
})();
let onUnauthorized = null;
/** Called by AuthContext on login/logout. Pass null to clear. */
export function setAuthToken(token) {
authToken = token || null;
try {
if (token) sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
else sessionStorage.removeItem(TOKEN_STORAGE_KEY);
} catch {
/* private browsing with storage disabled - the in-memory copy still works */
}
}
export function getAuthToken() {
return authToken;
}
/**
* Registered by AuthContext so an expired token anywhere in the app drops the
* session once, rather than leaving every panel to render its own 401 error.
*/
export function setUnauthorizedHandler(fn) {
onUnauthorized = fn;
}
function authHeaders() {
return authToken ? { Authorization: `Bearer ${authToken}` } : {};
}
class ApiError extends Error {
constructor(message, status) {
super(message);
@@ -11,7 +55,11 @@ async function request(path, options = {}) {
let res;
try {
res = await fetch(`${BASE}${path}`, {
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
headers: {
'Content-Type': 'application/json',
...authHeaders(),
...(options.headers || {}),
},
...options,
});
} catch {
@@ -30,6 +78,9 @@ async function request(path, options = {}) {
} catch {
/* ignore parse errors, keep generic message */
}
// 401 means the token is missing, expired or invalid - the session is over.
// 403 is a live session lacking a permission, so it must NOT log you out.
if (res.status === 401 && onUnauthorized) onUnauthorized(detail);
throw new ApiError(detail, res.status);
}
@@ -58,6 +109,15 @@ const SHOW_ALL_PRODUCTS_LIMIT = 100000;
export const api = {
getHealth: () => request('/api/health'),
// --- Auth ---
login: (username, password) =>
request('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
}),
getMe: () => request('/api/auth/me'),
getRoles: () => request('/api/auth/roles'),
getBrands: () => request('/api/brands'),
getBrandCategories: (brand) => request(`/api/brands/${encodeURIComponent(brand)}/categories`),
@@ -159,11 +219,15 @@ export const api = {
getNutritionEnrichmentStatus: () => request('/api/admin/nutrition-intelligence/status'),
// --- Excel / CSV Upload API ---
// Raw fetch rather than request(): the browser must set its own multipart
// Content-Type with the boundary, so the JSON default in request() would
// corrupt the body. The auth header still has to be added by hand here.
uploadFile: async (tabType, file) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch(`${BASE}/api/upload/${encodeURIComponent(tabType)}`, {
method: 'POST',
headers: authHeaders(),
body: formData,
});
if (!res.ok) {
@@ -172,6 +236,7 @@ export const api = {
const body = await res.json();
detail = body.detail || JSON.stringify(body);
} catch {}
if (res.status === 401 && onUnauthorized) onUnauthorized(detail);
throw new ApiError(detail, res.status);
}
return res.json();