- Replaced multiple global exception filters with a unified exception filter to streamline error handling across the application. - Removed deprecated AuthErrorFilter and GlobalExceptionFilter to reduce redundancy. - Enhanced SupportController to include new endpoints for listing, retrieving, and creating support cases, improving the support case management functionality. - Integrated SalesforceCaseService for better interaction with Salesforce data in support case operations. - Updated support case schemas to align with new requirements and ensure data consistency.
166 lines
5.1 KiB
TypeScript
166 lines
5.1 KiB
TypeScript
export { createClient, resolveBaseUrl } from "./runtime/client";
|
|
export type {
|
|
ApiClient,
|
|
AuthHeaderResolver,
|
|
CreateClientOptions,
|
|
QueryParams,
|
|
PathParams,
|
|
} from "./runtime/client";
|
|
export { ApiError, isApiError } from "./runtime/client";
|
|
|
|
// Re-export API helpers
|
|
export * from "./response-helpers";
|
|
|
|
// Import createClient for internal use
|
|
import { createClient, ApiError } from "./runtime/client";
|
|
import { logger } from "@/lib/logger";
|
|
|
|
/**
|
|
* Auth endpoints that should NOT trigger automatic logout on 401
|
|
* These are endpoints where 401 means "invalid credentials", not "session expired"
|
|
*/
|
|
const AUTH_ENDPOINTS = [
|
|
"/api/auth/login",
|
|
"/api/auth/signup",
|
|
"/api/auth/link-whmcs",
|
|
"/api/auth/set-password",
|
|
"/api/auth/reset-password",
|
|
"/api/auth/check-password-needed",
|
|
];
|
|
|
|
/**
|
|
* Check if a URL path is an auth endpoint
|
|
*/
|
|
function isAuthEndpoint(url: string): boolean {
|
|
try {
|
|
const urlPath = new URL(url).pathname;
|
|
return AUTH_ENDPOINTS.some(endpoint => urlPath.endsWith(endpoint));
|
|
} catch {
|
|
return AUTH_ENDPOINTS.some(endpoint => url.includes(endpoint));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract error message from API error body
|
|
* Handles both `{ message }` and `{ error: { message } }` formats
|
|
*/
|
|
function extractErrorMessage(body: unknown): string | null {
|
|
if (!body || typeof body !== "object") {
|
|
return null;
|
|
}
|
|
|
|
// Check for nested error.message format (standard API error response)
|
|
const bodyWithError = body as { error?: { message?: unknown } };
|
|
if (bodyWithError.error && typeof bodyWithError.error === "object") {
|
|
const errorMessage = bodyWithError.error.message;
|
|
if (typeof errorMessage === "string") {
|
|
return errorMessage;
|
|
}
|
|
}
|
|
|
|
// Check for top-level message
|
|
const bodyWithMessage = body as { message?: unknown };
|
|
if (typeof bodyWithMessage.message === "string") {
|
|
return bodyWithMessage.message;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Global error handler for API client
|
|
* Handles authentication errors and triggers logout when needed
|
|
*/
|
|
async function handleApiError(response: Response): Promise<void> {
|
|
// Don't import useAuthStore at module level to avoid circular dependencies
|
|
// We'll handle auth errors by dispatching a custom event that the auth system can listen to
|
|
|
|
// Only dispatch logout event for 401s on non-auth endpoints
|
|
// Auth endpoints (login, signup, etc.) return 401 for invalid credentials,
|
|
// which should NOT trigger logout - just show the error message
|
|
if (response.status === 401 && !isAuthEndpoint(response.url)) {
|
|
logger.warn("Received 401 Unauthorized response - triggering logout");
|
|
|
|
// Dispatch a custom event that the auth system will listen to
|
|
if (typeof window !== "undefined") {
|
|
window.dispatchEvent(
|
|
new CustomEvent("auth:unauthorized", {
|
|
detail: { url: response.url, status: response.status },
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
// Still throw the error so the calling code can handle it
|
|
let body: unknown;
|
|
let message = response.statusText || `Request failed with status ${response.status}`;
|
|
|
|
try {
|
|
const cloned = response.clone();
|
|
const contentType = cloned.headers.get("content-type");
|
|
if (contentType?.includes("application/json")) {
|
|
body = await cloned.json();
|
|
const extractedMessage = extractErrorMessage(body);
|
|
if (extractedMessage) {
|
|
message = extractedMessage;
|
|
}
|
|
}
|
|
} catch {
|
|
// Ignore body parse errors
|
|
}
|
|
|
|
throw new ApiError(message, response, body);
|
|
}
|
|
|
|
export const apiClient = createClient({
|
|
handleError: handleApiError,
|
|
});
|
|
|
|
// Query keys for React Query - matching the expected structure
|
|
export const queryKeys = {
|
|
auth: {
|
|
me: () => ["auth", "me"] as const,
|
|
session: () => ["auth", "session"] as const,
|
|
},
|
|
billing: {
|
|
invoices: (params?: Record<string, unknown>) => ["billing", "invoices", params] as const,
|
|
invoice: (id: string) => ["billing", "invoice", id] as const,
|
|
paymentMethods: () => ["billing", "payment-methods"] as const,
|
|
},
|
|
subscriptions: {
|
|
all: () => ["subscriptions"] as const,
|
|
list: (params?: Record<string, unknown>) => ["subscriptions", "list", params] as const,
|
|
active: () => ["subscriptions", "active"] as const,
|
|
stats: () => ["subscriptions", "stats"] as const,
|
|
detail: (id: string) => ["subscriptions", "detail", id] as const,
|
|
invoices: (id: number, params?: Record<string, unknown>) =>
|
|
["subscriptions", "invoices", id, params] as const,
|
|
},
|
|
dashboard: {
|
|
summary: () => ["dashboard", "summary"] as const,
|
|
},
|
|
catalog: {
|
|
products: () => ["catalog", "products"] as const,
|
|
internet: {
|
|
combined: () => ["catalog", "internet", "combined"] as const,
|
|
},
|
|
sim: {
|
|
combined: () => ["catalog", "sim", "combined"] as const,
|
|
},
|
|
vpn: {
|
|
combined: () => ["catalog", "vpn", "combined"] as const,
|
|
},
|
|
},
|
|
orders: {
|
|
list: () => ["orders", "list"] as const,
|
|
detail: (id: string | number) => ["orders", "detail", String(id)] as const,
|
|
},
|
|
support: {
|
|
cases: (params?: Record<string, unknown>) => ["support", "cases", params] as const,
|
|
case: (id: string) => ["support", "case", id] as const,
|
|
},
|
|
currency: {
|
|
default: () => ["currency", "default"] as const,
|
|
},
|
|
} as const;
|