feat: complete admin panel and backend admin api migration

This commit is contained in:
s.esmaeili
2026-09-13 21:02:25 +03:30
parent 03119cd655
commit 267fc7b7d1
239 changed files with 25431 additions and 935 deletions
+71 -58
View File
@@ -1,61 +1,74 @@
import type { CreateOrderDto, CustomerBalanceDto } from '@gold-bank/shared-types';
// Ported from legacy henza_admin/src/axios.js.
//
// Legacy pointed at the live PHP backend:
// baseURL = 'https://henzagold.com/oapi/admin/'
// The new backend is the local NestJS API. Legacy endpoint names are translated
// to clean REST paths by ./endpoints.ts — see the mapping-layer note there.
import axios from 'axios';
import { tokenName } from '../utils';
import { mapEndpoint } from './endpoints';
const BASE = '/api';
export const baseURL = import.meta.env.VITE_API_BASE_URL ?? '/api/';
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
headers: { 'Content-Type': 'application/json' },
...options,
});
if (!res.ok) {
const body = await res.text();
throw new Error(`API ${path} failed (${res.status}): ${body}`);
}
return res.json();
}
export interface CustomerSummary {
id: string;
fullName: string;
mobile: string;
goldDebtGrams: string;
fiatDebtRial: string;
goldCreditLimitGrams: string;
}
export interface ProductVariantSummary {
id: string;
title: string;
rashWeightGrams: string;
defaultWagePercent: string;
}
export interface ProductSummary {
id: string;
title: string;
variants: ProductVariantSummary[];
}
export interface OrderDetail {
id: string;
orderNumber: string;
status: string;
totalGoldWeightGrams: string;
wageWeightGrams: string;
flatRialCharge: string;
isSuspendedOverCreditLimit: boolean;
deliveredAt: string | null;
items: Array<{ id: string; quantity: number; computedWeightGrams: string }>;
statusEvents: Array<{ toStatus: string; note: string | null; createdAt: string }>;
}
export const api = {
listCustomers: () => request<CustomerSummary[]>('/customers'),
getCustomerBalance: (id: string) => request<CustomerBalanceDto>(`/accounting/customers/${id}/balance`),
listProducts: () => request<ProductSummary[]>('/catalog/products'),
createOrder: (dto: CreateOrderDto) =>
request<OrderDetail>('/orders', { method: 'POST', body: JSON.stringify(dto) }),
getOrder: (id: string) => request<OrderDetail>(`/orders/${id}`),
updateOrderStatus: (id: string, status: string, note?: string) =>
request<OrderDetail>(`/orders/${id}/status`, { method: 'PATCH', body: JSON.stringify({ status, note }) }),
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Accept: 'application/json',
};
const initialToken = localStorage.getItem(tokenName);
if (initialToken) {
headers['Authorization'] = 'Bearer ' + initialToken;
}
const ax = axios.create({
baseURL,
headers,
});
/**
* Legacy re-set the auth header on every request so a token acquired after boot
* was picked up without recreating the instance. Kept — plus the endpoint
* rewrite, and without legacy's habit of sending a literal "Bearer null" when
* no token existed.
*/
ax.interceptors.request.use(
(config) => {
if (config.url) {
config.url = mapEndpoint(config.url);
}
const token = localStorage.getItem(tokenName);
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
} else {
delete config.headers['Authorization'];
}
return config;
},
(error) => Promise.reject(error),
);
/**
* NOT IN LEGACY: legacy had no response interceptor at all, so an expired token
* produced a silent failure on every subsequent call with no path back to login.
* A 401 now clears the stale session and redirects once.
*
* The redirect is a hard location assignment rather than a router push, to avoid
* importing the router here (circular: router -> stores -> client -> router).
*/
let redirecting = false;
ax.interceptors.response.use(
(response) => response,
(error) => {
if (error?.response?.status === 401 && !redirecting) {
const onLoginPage = window.location.pathname.endsWith('/auth/login');
if (!onLoginPage) {
redirecting = true;
localStorage.removeItem(tokenName);
window.location.assign(`${import.meta.env.BASE_URL}auth/login`);
}
}
return Promise.reject(error);
},
);
export default ax;