Send the auth token on every API call; build against the API's own domain

Seven call sites used fetch() directly instead of the api client in
src/api/client.js, so they sent no Authorization header. Four of them hit
endpoints the backend guards with a permission, and every one of those returned
401 for any signed-in user:

  AdminPage  /api/admin/training/upload-dataset      (upload_train_test)
  AdminPage  /api/admin/training/allocate-discounts  (allocate_discounts)
  UserPage   /api/user/products/add                  (add_product)
  UserPage   /api/user/products/upload-file          (upload_batch_products)

The remaining three hit public GETs and worked, but bypassed VITE_API_BASE_URL
and the central 401 handler just the same.

Route all seven through the client and add the endpoints it was missing.
Multipart uploads still need a raw fetch, because the browser has to set its
own Content-Type to carry the boundary, so that is now one upload() helper that
attaches the auth header and routes 401s to the logout handler rather than
three copies that did neither.

handleAllocateDiscounts only acted on res.ok, so a 401 or 403 left the panel
looking idle with no indication that nothing had been allocated. It surfaces
the error now.

Take VITE_API_BASE_URL as a Docker build arg. The app is served from
catalogue.nearle.ai.in and the API from mcp.catalogue.nearle.ai.in, and Vite
inlines env vars at build time, so setting this on the running container does
nothing - it has to reach npm run build. Defaults to empty, which keeps
requests relative for the same-origin nginx proxy.

Also revoke the object URL after the sample-CSV download.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Suriyakumarvijayanayagam
2026-08-13 12:31:06 +05:30
parent dae0b3e0fb
commit ea677c72df
5 changed files with 149 additions and 123 deletions

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { api } from '../api/client';
import { NavigationHeader } from '../components/NavigationHeader';
import { ProductCard } from '../components/ProductCard';
import { ProductModal } from '../components/ProductModal';
@@ -165,18 +166,13 @@ export function UserPage() {
const fetchStoreProductsCards = async (storeId) => {
setLoadingCards(true);
try {
const res = await fetch(`/api/stores/${storeId}/products?limit=100`);
if (res.ok) {
const data = await res.json();
const itemsList = Array.isArray(data)
? data
: Array.isArray(data?.products)
? data.products
: [];
setStoreProductsRaw(itemsList);
} else {
setStoreProductsRaw([]);
}
const data = await api.getStoreProducts(storeId, { limit: 100 });
const itemsList = Array.isArray(data)
? data
: Array.isArray(data?.products)
? data.products
: [];
setStoreProductsRaw(itemsList);
} catch (e) {
console.warn('Failed to load store products for cards:', e);
setStoreProductsRaw([]);
@@ -193,18 +189,13 @@ export function UserPage() {
if (!bName) return;
setLoadingBrand(true);
try {
const res = await fetch(`/api/brands/${encodeURIComponent(bName)}/products?limit=50`);
if (res.ok) {
const data = await res.json();
const itemsList = Array.isArray(data)
? data
: Array.isArray(data?.products)
? data.products
: [];
setBrandProducts(itemsList);
} else {
setBrandProducts([]);
}
const data = await api.getBrandProducts(bName, { limit: 50 });
const itemsList = Array.isArray(data)
? data
: Array.isArray(data?.products)
? data.products
: [];
setBrandProducts(itemsList);
} catch (e) {
console.warn('Failed to fetch brand products', e);
setBrandProducts([]);
@@ -250,18 +241,7 @@ export function UserPage() {
image_url: imageUrl || undefined,
};
const res = await fetch('/api/user/products/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.detail || 'Failed to add product');
}
const data = await res.json();
const data = await api.addUserProduct(payload);
setSingleResult(data);
setSubmittingSingle(false);
@@ -307,21 +287,8 @@ export function UserPage() {
setBatchResult(null);
setUploadingBatch(true);
const formData = new FormData();
formData.append('file', batchFile);
try {
const res = await fetch('/api/user/products/upload-file', {
method: 'POST',
body: formData,
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.detail || 'Batch file upload failed');
}
const data = await res.json();
const data = await api.uploadUserProductsFile(batchFile);
setBatchResult(data);
setUploadingBatch(false);
@@ -367,6 +334,7 @@ export function UserPage() {
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
// Convert Store products into rich Card objects with 100% type-safe conversions and title-specific images