feat(POS-006): completed feature
This commit is contained in:
40
project/apps/pos/src/lib/api-client.ts
Normal file
40
project/apps/pos/src/lib/api-client.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${API}${path}`, {
|
||||
...init,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(init?.headers ?? {}),
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ message: res.statusText }));
|
||||
throw Object.assign(new Error((err as { message?: string }).message ?? 'Request failed'), { status: res.status });
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export const posApi = {
|
||||
/** Get current terminal info. Requires x-terminal-id header set by middleware. */
|
||||
me: () => apiFetch('/pos/terminals/me'),
|
||||
/** Bind terminal with code. */
|
||||
bind: (bindingCode: string) =>
|
||||
apiFetch('/pos/terminals/bind', { method: 'POST', body: JSON.stringify({ bindingCode }) }),
|
||||
/** Get POS config (store + terminal + payment methods + session status). */
|
||||
config: () => apiFetch('/pos/config'),
|
||||
/** List products by query. */
|
||||
searchProducts: (q: string, storeId?: string, limit = 20) =>
|
||||
apiFetch(`/pos/products/search?q=${encodeURIComponent(q)}&storeId=${storeId ?? ''}&limit=${limit}`),
|
||||
/** Get product by EAN. */
|
||||
productByEan: (ean: string) => apiFetch(`/pos/products/by-ean/${encodeURIComponent(ean)}`),
|
||||
/** Get product by SKU. */
|
||||
productBySku: (sku: string) => apiFetch(`/pos/products/by-sku/${encodeURIComponent(sku)}`),
|
||||
};
|
||||
|
||||
export const authApi = {
|
||||
login: (email: string, password: string) =>
|
||||
apiFetch('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
|
||||
logout: () => apiFetch('/auth/logout', { method: 'POST' }),
|
||||
};
|
||||
12
project/apps/pos/src/lib/idempotency.ts
Normal file
12
project/apps/pos/src/lib/idempotency.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/** Generate a client-side idempotency key (UUID v4). */
|
||||
export function generateIdempotencyKey(): string {
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
// Fallback
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
4
project/apps/pos/src/lib/money.ts
Normal file
4
project/apps/pos/src/lib/money.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/** Format cents as EUR string. */
|
||||
export function formatPrice(cents: number): string {
|
||||
return `€${(cents / 100).toFixed(2)}`;
|
||||
}
|
||||
15
project/apps/pos/src/lib/permissions.ts
Normal file
15
project/apps/pos/src/lib/permissions.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/** POS permissions used in the POS app. */
|
||||
export type PosPermission =
|
||||
| 'POS_SELL'
|
||||
| 'POS_REFUND'
|
||||
| 'POS_DISCOUNT'
|
||||
| 'POS_VOID'
|
||||
| 'POS_OPEN_REGISTER'
|
||||
| 'POS_CLOSE_REGISTER';
|
||||
|
||||
/** Map role → granted permissions. */
|
||||
export const POS_ROLE_PERMISSIONS: Record<string, PosPermission[]> = {
|
||||
admin: ['POS_SELL', 'POS_REFUND', 'POS_DISCOUNT', 'POS_VOID', 'POS_OPEN_REGISTER', 'POS_CLOSE_REGISTER'],
|
||||
pos_manager: ['POS_SELL', 'POS_REFUND', 'POS_DISCOUNT', 'POS_VOID', 'POS_OPEN_REGISTER', 'POS_CLOSE_REGISTER'],
|
||||
pos_cashier: ['POS_SELL'],
|
||||
};
|
||||
Reference in New Issue
Block a user