This commit is contained in:
NotII
2025-07-20 23:34:42 +01:00
parent 0617ea5289
commit b329c8422d
6 changed files with 294 additions and 42 deletions

View File

@@ -47,6 +47,96 @@ export interface CustomerResponse {
success?: boolean;
}
// Add cache invalidation and order refresh utilities
interface CacheEntry {
data: any;
timestamp: number;
ttl: number; // Time to live in milliseconds
}
class ApiCache {
private cache = new Map<string, CacheEntry>();
private orderRefreshCallbacks = new Set<() => void>();
get(key: string): any | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > entry.ttl) {
this.cache.delete(key);
return null;
}
return entry.data;
}
set(key: string, data: any, ttl: number = 60000): void { // Default 1 minute TTL
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl
});
}
invalidate(pattern?: string): void {
if (pattern) {
// Remove entries matching pattern
for (const [key] of this.cache) {
if (key.includes(pattern)) {
this.cache.delete(key);
}
}
} else {
// Clear all cache
this.cache.clear();
}
}
// Order-specific cache invalidation
invalidateOrderData(orderId?: string): void {
if (orderId) {
this.invalidate(`orders/${orderId}`);
this.invalidate(`orders?`); // Invalidate order lists
} else {
this.invalidate('orders');
}
// Show a subtle notification when data is refreshed
if (typeof window !== 'undefined') {
console.log('🔄 Order data cache invalidated, refreshing components...');
}
// Trigger order refresh callbacks
this.orderRefreshCallbacks.forEach(callback => {
try {
callback();
} catch (error) {
console.error('Error in order refresh callback:', error);
}
});
}
// Register callback for order data refresh
onOrderRefresh(callback: () => void): () => void {
this.orderRefreshCallbacks.add(callback);
// Return unsubscribe function
return () => {
this.orderRefreshCallbacks.delete(callback);
};
}
}
// Global cache instance
const apiCache = new ApiCache();
// Export cache utilities
export const cacheUtils = {
invalidateOrderData: (orderId?: string) => apiCache.invalidateOrderData(orderId),
onOrderRefresh: (callback: () => void) => apiCache.onOrderRefresh(callback),
invalidateAll: () => apiCache.invalidate(),
};
/**
* Normalizes a URL to ensure it has the correct /api prefix
* This prevents double prefixing which causes API errors
@@ -261,4 +351,46 @@ export const getCustomers = async (page: number = 1, limit: number = 25): Promis
*/
export const getCustomerDetails = async (userId: string): Promise<CustomerStats> => {
return clientFetch(`/customers/${userId}`);
};
};
/**
* Enhanced client-side fetch function with caching and automatic invalidation
*/
export async function clientFetchWithCache<T = any>(
url: string,
options: RequestInit = {},
cacheKey?: string,
ttl?: number
): Promise<T> {
// Check cache first for GET requests
if (options.method === 'GET' || !options.method) {
const cached = apiCache.get(cacheKey || url);
if (cached) {
return cached;
}
}
// Make the request
const result = await clientFetch<T>(url, options);
// Cache GET requests
if ((options.method === 'GET' || !options.method) && cacheKey) {
apiCache.set(cacheKey, result, ttl);
}
// Invalidate cache for mutations that affect orders
if (options.method && ['PUT', 'POST', 'DELETE', 'PATCH'].includes(options.method)) {
if (url.includes('/orders/') && url.includes('/status')) {
// Order status update - invalidate order cache
const orderIdMatch = url.match(/\/orders\/([^\/]+)\/status/);
if (orderIdMatch) {
apiCache.invalidateOrderData(orderIdMatch[1]);
}
} else if (url.includes('/orders')) {
// General order mutation
apiCache.invalidateOrderData();
}
}
return result;
}