feat: SAC 0.7.3 custom error pages 401/403/404/429
Centered logo pages with event explanation and allowed actions; router and apiFetch redirect to them on auth, permission, not-found, and rate-limit errors. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+34
-7
@@ -31,29 +31,56 @@ export function clearToken(): void {
|
||||
localStorage.removeItem(ROLE_KEY);
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(message: string, status: number) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export function redirectToErrorPage(status: 401 | 403 | 429): void {
|
||||
window.location.href = `/${status}`;
|
||||
}
|
||||
|
||||
export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
const headers = new Headers(init.headers);
|
||||
if (!headers.has("Content-Type") && init.body) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
const token = getToken();
|
||||
const hadToken = !!token;
|
||||
if (token) {
|
||||
headers.set("Authorization", `Bearer ${token}`);
|
||||
}
|
||||
const res = await fetch(path, { ...init, headers });
|
||||
const text = await res.text();
|
||||
if (res.status === 401) {
|
||||
clearToken();
|
||||
window.location.href = "/login";
|
||||
throw new Error("Unauthorized");
|
||||
if (hadToken) {
|
||||
clearToken();
|
||||
redirectToErrorPage(401);
|
||||
}
|
||||
throw new ApiError(parseApiError(text) || "Unauthorized", 401);
|
||||
}
|
||||
if (res.status === 403) {
|
||||
throw new Error("Forbidden");
|
||||
if (hadToken) {
|
||||
redirectToErrorPage(403);
|
||||
}
|
||||
throw new ApiError(parseApiError(text) || "Forbidden", 403);
|
||||
}
|
||||
if (res.status === 429) {
|
||||
redirectToErrorPage(429);
|
||||
throw new ApiError(parseApiError(text) || "Too many requests", 429);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(parseApiError(text) || res.statusText);
|
||||
throw new ApiError(parseApiError(text) || res.statusText, res.status);
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
if (!text) {
|
||||
return undefined as T;
|
||||
}
|
||||
return JSON.parse(text) as T;
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
|
||||
Reference in New Issue
Block a user