Add Milestones 1 & 2: full-stack POS foundation with admin UI
- Node/Express/TypeScript API under /api/v1 with JWT auth (login, refresh, logout, /me)
- Prisma schema: vendors, users, roles, products, categories, taxes, transactions
- SQLite for local dev; Postgres via docker-compose for production
- Full CRUD routes for vendors, users, categories, taxes, products with Zod validation and RBAC
- Paginated list endpoints scoped per vendor; refresh token rotation
- React/TypeScript admin SPA (Vite): login, protected routing, sidebar layout
- Pages: Dashboard, Catalog (tabbed Products/Categories/Taxes), Users, Vendor Settings
- Shared UI: Table, Modal, FormField, Btn, PageHeader components
- Multi-stage Dockerfile; docker-compose with Postgres healthcheck
- Seed script with demo vendor and owner account
- INSTRUCTIONS.md, ROADMAP.md, .claude/launch.json for dev server config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:18:04 -05:00
|
|
|
const BASE = "/api/v1";
|
|
|
|
|
|
|
|
|
|
export function setTokens(accessToken: string, refreshToken: string) {
|
|
|
|
|
localStorage.setItem("accessToken", accessToken);
|
|
|
|
|
localStorage.setItem("refreshToken", refreshToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function clearTokens() {
|
|
|
|
|
localStorage.removeItem("accessToken");
|
|
|
|
|
localStorage.removeItem("refreshToken");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 07:06:59 -05:00
|
|
|
function getToken(): string | null {
|
|
|
|
|
return localStorage.getItem("accessToken");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Single in-flight refresh promise so concurrent 401s don't fire multiple refreshes
|
|
|
|
|
let refreshPromise: Promise<string | null> | null = null;
|
|
|
|
|
|
|
|
|
|
async function tryRefresh(): Promise<string | null> {
|
|
|
|
|
const refreshToken = localStorage.getItem("refreshToken");
|
|
|
|
|
if (!refreshToken) return null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`${BASE}/auth/refresh`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ refreshToken }),
|
|
|
|
|
});
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
clearTokens();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const data = await res.json() as { accessToken: string; refreshToken: string };
|
|
|
|
|
setTokens(data.accessToken, data.refreshToken);
|
|
|
|
|
return data.accessToken;
|
|
|
|
|
} catch {
|
|
|
|
|
clearTokens();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
|
|
|
const doFetch = async (token: string | null) => {
|
|
|
|
|
const headers: Record<string, string> = {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
...(options.headers as Record<string, string>),
|
|
|
|
|
};
|
|
|
|
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
|
|
|
return fetch(`${BASE}${path}`, { ...options, headers });
|
Add Milestones 1 & 2: full-stack POS foundation with admin UI
- Node/Express/TypeScript API under /api/v1 with JWT auth (login, refresh, logout, /me)
- Prisma schema: vendors, users, roles, products, categories, taxes, transactions
- SQLite for local dev; Postgres via docker-compose for production
- Full CRUD routes for vendors, users, categories, taxes, products with Zod validation and RBAC
- Paginated list endpoints scoped per vendor; refresh token rotation
- React/TypeScript admin SPA (Vite): login, protected routing, sidebar layout
- Pages: Dashboard, Catalog (tabbed Products/Categories/Taxes), Users, Vendor Settings
- Shared UI: Table, Modal, FormField, Btn, PageHeader components
- Multi-stage Dockerfile; docker-compose with Postgres healthcheck
- Seed script with demo vendor and owner account
- INSTRUCTIONS.md, ROADMAP.md, .claude/launch.json for dev server config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:18:04 -05:00
|
|
|
};
|
|
|
|
|
|
2026-03-21 07:06:59 -05:00
|
|
|
let res = await doFetch(getToken());
|
|
|
|
|
|
|
|
|
|
// On 401, attempt one token refresh then retry
|
|
|
|
|
if (res.status === 401) {
|
|
|
|
|
if (!refreshPromise) {
|
|
|
|
|
refreshPromise = tryRefresh().finally(() => { refreshPromise = null; });
|
|
|
|
|
}
|
|
|
|
|
const newToken = await refreshPromise;
|
|
|
|
|
|
|
|
|
|
if (!newToken) {
|
|
|
|
|
// Refresh failed — signal the app to go to login
|
|
|
|
|
window.dispatchEvent(new CustomEvent("auth:logout"));
|
|
|
|
|
throw new Error("Session expired. Please sign in again.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = await doFetch(newToken);
|
|
|
|
|
}
|
Add Milestones 1 & 2: full-stack POS foundation with admin UI
- Node/Express/TypeScript API under /api/v1 with JWT auth (login, refresh, logout, /me)
- Prisma schema: vendors, users, roles, products, categories, taxes, transactions
- SQLite for local dev; Postgres via docker-compose for production
- Full CRUD routes for vendors, users, categories, taxes, products with Zod validation and RBAC
- Paginated list endpoints scoped per vendor; refresh token rotation
- React/TypeScript admin SPA (Vite): login, protected routing, sidebar layout
- Pages: Dashboard, Catalog (tabbed Products/Categories/Taxes), Users, Vendor Settings
- Shared UI: Table, Modal, FormField, Btn, PageHeader components
- Multi-stage Dockerfile; docker-compose with Postgres healthcheck
- Seed script with demo vendor and owner account
- INSTRUCTIONS.md, ROADMAP.md, .claude/launch.json for dev server config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:18:04 -05:00
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const body = await res.json().catch(() => ({}));
|
2026-03-21 07:06:59 -05:00
|
|
|
const message = (body as { error?: { message?: string } })?.error?.message ?? `HTTP ${res.status}`;
|
Add Milestones 1 & 2: full-stack POS foundation with admin UI
- Node/Express/TypeScript API under /api/v1 with JWT auth (login, refresh, logout, /me)
- Prisma schema: vendors, users, roles, products, categories, taxes, transactions
- SQLite for local dev; Postgres via docker-compose for production
- Full CRUD routes for vendors, users, categories, taxes, products with Zod validation and RBAC
- Paginated list endpoints scoped per vendor; refresh token rotation
- React/TypeScript admin SPA (Vite): login, protected routing, sidebar layout
- Pages: Dashboard, Catalog (tabbed Products/Categories/Taxes), Users, Vendor Settings
- Shared UI: Table, Modal, FormField, Btn, PageHeader components
- Multi-stage Dockerfile; docker-compose with Postgres healthcheck
- Seed script with demo vendor and owner account
- INSTRUCTIONS.md, ROADMAP.md, .claude/launch.json for dev server config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:18:04 -05:00
|
|
|
throw new Error(message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.json() as Promise<T>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const api = {
|
2026-03-21 07:06:59 -05:00
|
|
|
get: <T>(path: string) => request<T>(path),
|
|
|
|
|
post: <T>(path: string, body: unknown) => request<T>(path, { method: "POST", body: JSON.stringify(body) }),
|
|
|
|
|
put: <T>(path: string, body: unknown) => request<T>(path, { method: "PUT", body: JSON.stringify(body) }),
|
|
|
|
|
patch: <T>(path: string, body: unknown) => request<T>(path, { method: "PATCH", body: JSON.stringify(body) }),
|
|
|
|
|
delete: <T>(path: string) => request<T>(path, { method: "DELETE" }),
|
Add Milestones 1 & 2: full-stack POS foundation with admin UI
- Node/Express/TypeScript API under /api/v1 with JWT auth (login, refresh, logout, /me)
- Prisma schema: vendors, users, roles, products, categories, taxes, transactions
- SQLite for local dev; Postgres via docker-compose for production
- Full CRUD routes for vendors, users, categories, taxes, products with Zod validation and RBAC
- Paginated list endpoints scoped per vendor; refresh token rotation
- React/TypeScript admin SPA (Vite): login, protected routing, sidebar layout
- Pages: Dashboard, Catalog (tabbed Products/Categories/Taxes), Users, Vendor Settings
- Shared UI: Table, Modal, FormField, Btn, PageHeader components
- Multi-stage Dockerfile; docker-compose with Postgres healthcheck
- Seed script with demo vendor and owner account
- INSTRUCTIONS.md, ROADMAP.md, .claude/launch.json for dev server config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:18:04 -05:00
|
|
|
};
|