Add OTP login flow and multi-cafe role switching
Introduce an OTP input box on login/register, surface user roles and a cafe chooser, add a dashboard switch button in the POS screen, and register OTP validators explicitly to survive Docker layer caching. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -76,7 +76,9 @@ export async function apiGetPaged<T>(url: string): Promise<{ items: T[]; meta: P
|
||||
export class ApiClientError extends Error {
|
||||
constructor(
|
||||
public readonly code: string,
|
||||
message: string
|
||||
message: string,
|
||||
/** Payload returned alongside a non-success response (e.g. CHOOSE_CAFE choices). */
|
||||
public readonly payload?: unknown
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiClientError";
|
||||
@@ -87,7 +89,7 @@ export async function apiPost<T, B = unknown>(url: string, body?: B): Promise<T>
|
||||
const { data } = await api.post<ApiResponse<T>>(url, body);
|
||||
if (!data.success || data.data === undefined) {
|
||||
const code = data.error?.code ?? "REQUEST_FAILED";
|
||||
throw new ApiClientError(code, data.error?.message ?? "Request failed");
|
||||
throw new ApiClientError(code, data.error?.message ?? "Request failed", data.data);
|
||||
}
|
||||
return data.data;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ export interface ApiResponse<T> {
|
||||
error?: { code: string; message: string; field?: string };
|
||||
}
|
||||
|
||||
export interface CafeMembership {
|
||||
cafeId: string;
|
||||
cafeName: string;
|
||||
role: string;
|
||||
planTier: string;
|
||||
}
|
||||
|
||||
export interface AuthTokenResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
@@ -15,6 +22,12 @@ export interface AuthTokenResponse {
|
||||
language: string;
|
||||
actor?: string;
|
||||
branchId?: string | null;
|
||||
memberships?: CafeMembership[] | null;
|
||||
}
|
||||
|
||||
/** Returned (in the data field) when a phone belongs to multiple cafés. */
|
||||
export interface CafeChoicesResponse {
|
||||
cafes: CafeMembership[];
|
||||
}
|
||||
|
||||
export interface MenuCategory {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Maps backend EmployeeRole names to i18n keys under the "roles" namespace.
|
||||
* Backend enum: Owner, Manager, Cashier, Waiter, Chef, Delivery.
|
||||
*/
|
||||
export type EmployeeRoleName =
|
||||
| "Owner"
|
||||
| "Manager"
|
||||
| "Cashier"
|
||||
| "Waiter"
|
||||
| "Chef"
|
||||
| "Delivery";
|
||||
|
||||
export const ROLE_KEYS: Record<string, string> = {
|
||||
Owner: "owner",
|
||||
Manager: "manager",
|
||||
Cashier: "cashier",
|
||||
Waiter: "waiter",
|
||||
Chef: "chef",
|
||||
Delivery: "delivery",
|
||||
};
|
||||
|
||||
export function roleKey(role: string | undefined | null): string {
|
||||
if (!role) return "unknown";
|
||||
return ROLE_KEYS[role] ?? "unknown";
|
||||
}
|
||||
|
||||
/** Tailwind classes for a colored role badge. */
|
||||
export function roleBadgeClass(role: string | undefined | null): string {
|
||||
switch (role) {
|
||||
case "Owner":
|
||||
return "bg-primary/10 text-primary border-primary/30";
|
||||
case "Manager":
|
||||
return "bg-violet-50 text-violet-700 border-violet-200";
|
||||
case "Cashier":
|
||||
return "bg-blue-50 text-blue-700 border-blue-200";
|
||||
case "Chef":
|
||||
return "bg-amber-50 text-amber-700 border-amber-200";
|
||||
case "Waiter":
|
||||
return "bg-emerald-50 text-emerald-700 border-emerald-200";
|
||||
case "Delivery":
|
||||
return "bg-orange-50 text-orange-700 border-orange-200";
|
||||
default:
|
||||
return "bg-muted text-muted-foreground border-border";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user