veza/apps/web/src/services/api/helpers.ts
senke 5f88c56113 fix: UI remediation Phase 1 (S0-S5) + Phase 2 Sprint 6 shadow system
Phase 1:
- S0: Fix open redirect (safeNavigate), delete AuthContext/legacy auth, encrypt API keys, gitignore .env files
- S1: Split client.ts god object into 5 modules, unify toast system, delete unused Sidebar
- S2: Add glass button variant, migrate 32 z-index to SUMI tokens, fix card dark mode
- S3: Skip nav link, aria-hidden on icons, focus-visible ring fixes, alt attrs, aria-live regions
- S4: React.memo on list items, fix key={index}, loading=lazy on images
- S5: Branded loading screen, page transitions respect reduced-motion, LikeButton micro-interaction, i18n sidebar/header

Phase 2 Sprint 6:
- Wire Tailwind shadow utilities to SUMI tokens in @theme block (fixes 50+ files)
- Define shadow-card/shadow-card-hover tokens
- Remove dark:shadow-none workarounds from card.tsx (SUMI handles per-theme shadows)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 10:13:44 +01:00

165 lines
4 KiB
TypeScript

/**
* S1.1: API helper utilities
* Extracted from client.ts — Cancellable requests, deduplication, utilities
*/
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { logger } from '@/utils/logger';
import { requestDeduplication } from '../requestDeduplication';
import { responseCache } from '../responseCache';
import { apiClient } from './httpClient';
/**
* Edge 2.2: Create a cancellable request with AbortController support.
*/
export function createCancellableRequest<T>(
requestFn: (signal: AbortSignal) => Promise<T>,
): { request: Promise<T>; abort: () => void } {
const abortController = new AbortController();
const signal = abortController.signal;
const request = requestFn(signal).catch((error) => {
if (
axios.isCancel(error) ||
error.name === 'AbortError' ||
signal.aborted
) {
throw error;
}
throw error;
});
return {
request,
abort: () => {
if (!signal.aborted) {
abortController.abort();
}
},
};
}
/**
* Edge 2.2: Create a request with automatic timeout cancellation.
*/
export function createRequestWithTimeout<T>(
requestFn: (signal: AbortSignal) => Promise<T>,
timeoutMs: number,
): { request: Promise<T>; abort: () => void } {
const abortController = new AbortController();
const signal = abortController.signal;
const timeoutId = setTimeout(() => {
if (!signal.aborted) {
abortController.abort();
}
}, timeoutMs);
const request = requestFn(signal)
.catch((error) => {
if (
axios.isCancel(error) ||
error.name === 'AbortError' ||
signal.aborted
) {
throw error;
}
throw error;
})
.finally(() => {
clearTimeout(timeoutId);
});
return {
request,
abort: () => {
clearTimeout(timeoutId);
if (!signal.aborted) {
abortController.abort();
}
},
};
}
/**
* FE-API-016/017: Enhanced API client with deduplication and caching
*/
export const deduplicatedApiClient = {
get: <T = any>(url: string, config?: InternalAxiosRequestConfig) => {
if (!(config as any)?._disableCache) {
const cachedResponse = responseCache.get({
...config,
method: 'GET',
url,
});
if (cachedResponse) {
logger.debug(`[API] Using cached response for: ${url}`);
return Promise.resolve(cachedResponse as AxiosResponse<T>);
}
}
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'GET', url },
() => apiClient.get<T>(url, config),
);
},
post: <T = any>(
url: string,
data?: any,
config?: InternalAxiosRequestConfig,
) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'POST', url, data },
() => apiClient.post<T>(url, data, config),
);
},
put: <T = any>(
url: string,
data?: any,
config?: InternalAxiosRequestConfig,
) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'PUT', url, data },
() => apiClient.put<T>(url, data, config),
);
},
patch: <T = any>(
url: string,
data?: any,
config?: InternalAxiosRequestConfig,
) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'PATCH', url, data },
() => apiClient.patch<T>(url, data, config),
);
},
delete: <T = any>(url: string, config?: InternalAxiosRequestConfig) => {
return requestDeduplication.getOrCreateRequest(
{ ...config, method: 'DELETE', url },
() => apiClient.delete<T>(url, config),
);
},
};
/**
* Edge 2.3: Check if a request is slow
*/
export function isSlowRequest(
config?: InternalAxiosRequestConfig,
): boolean {
if (!config) return false;
return (config as any)?._isSlowRequest === true;
}
/**
* Edge 2.3: Get request duration in milliseconds
*/
export function getRequestDuration(
config?: InternalAxiosRequestConfig,
): number | undefined {
if (!config) return undefined;
return (config as any)?._requestDuration;
}