update
This commit is contained in:
@@ -2,6 +2,9 @@ import apiClient from './apiClient';
|
||||
|
||||
/**
|
||||
* Banner API Service
|
||||
*
|
||||
* NOTE: To avoid hammering the API, we add a very lightweight
|
||||
* in-memory + optional localStorage cache for read endpoints.
|
||||
*/
|
||||
|
||||
export interface Banner {
|
||||
@@ -27,28 +30,211 @@ export interface BannerListResponse {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// ---------- Simple client-side cache ----------
|
||||
|
||||
type CacheEntry = {
|
||||
timestamp: number;
|
||||
response: BannerListResponse;
|
||||
};
|
||||
|
||||
const BANNER_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
const memoryCache = new Map<string, CacheEntry>();
|
||||
|
||||
const getCacheKey = (position?: string) =>
|
||||
position ? `banners:position:${position}` : 'banners:active';
|
||||
|
||||
const isCacheValid = (entry: CacheEntry | undefined) => {
|
||||
if (!entry) return false;
|
||||
return Date.now() - entry.timestamp < BANNER_CACHE_TTL_MS;
|
||||
};
|
||||
|
||||
const loadFromStorage = (key: string): CacheEntry | undefined => {
|
||||
if (typeof window === 'undefined') return undefined;
|
||||
try {
|
||||
const raw = window.localStorage.getItem(key);
|
||||
if (!raw) return undefined;
|
||||
const parsed = JSON.parse(raw) as CacheEntry;
|
||||
return isCacheValid(parsed) ? parsed : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const saveToStorage = (key: string, entry: CacheEntry) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
window.localStorage.setItem(key, JSON.stringify(entry));
|
||||
} catch {
|
||||
// ignore storage errors (quota, disabled, etc.)
|
||||
}
|
||||
};
|
||||
|
||||
const getCachedOrFetch = async (
|
||||
key: string,
|
||||
fetcher: () => Promise<BannerListResponse>
|
||||
): Promise<BannerListResponse> => {
|
||||
// 1) Check in-memory cache
|
||||
const inMemory = memoryCache.get(key);
|
||||
if (isCacheValid(inMemory)) {
|
||||
return inMemory!.response;
|
||||
}
|
||||
|
||||
// 2) Check localStorage cache (fills memory cache if valid)
|
||||
const fromStorage = loadFromStorage(key);
|
||||
if (fromStorage) {
|
||||
memoryCache.set(key, fromStorage);
|
||||
return fromStorage.response;
|
||||
}
|
||||
|
||||
// 3) Fetch from API and cache result
|
||||
const response = await fetcher();
|
||||
const entry: CacheEntry = {
|
||||
timestamp: Date.now(),
|
||||
response,
|
||||
};
|
||||
memoryCache.set(key, entry);
|
||||
saveToStorage(key, entry);
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get banners by position
|
||||
*
|
||||
* Cached per-position on the client for a short TTL.
|
||||
*/
|
||||
export const getBannersByPosition = async (
|
||||
position: string = 'home'
|
||||
): Promise<BannerListResponse> => {
|
||||
const response = await apiClient.get('/banners', {
|
||||
params: { position },
|
||||
const key = getCacheKey(position);
|
||||
return getCachedOrFetch(key, async () => {
|
||||
const response = await apiClient.get('/banners', {
|
||||
params: { position },
|
||||
});
|
||||
return response.data;
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all active banners
|
||||
*
|
||||
* Cached on the client for a short TTL.
|
||||
*/
|
||||
export const getActiveBanners = async ():
|
||||
export const getActiveBanners = async ():
|
||||
Promise<BannerListResponse> => {
|
||||
const response = await apiClient.get('/banners');
|
||||
const key = getCacheKey();
|
||||
return getCachedOrFetch(key, async () => {
|
||||
const response = await apiClient.get('/banners');
|
||||
return response.data;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all banners (admin)
|
||||
*/
|
||||
export const getAllBanners = async (
|
||||
params?: { position?: string; page?: number; limit?: number }
|
||||
): Promise<BannerListResponse> => {
|
||||
const response = await apiClient.get('/banners', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Get banner by ID
|
||||
*/
|
||||
export const getBannerById = async (
|
||||
id: number
|
||||
): Promise<{ success: boolean; data: { banner: Banner } }> => {
|
||||
const response = await apiClient.get(`/banners/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create banner
|
||||
*/
|
||||
export const createBanner = async (
|
||||
data: {
|
||||
title: string;
|
||||
description?: string;
|
||||
image_url: string;
|
||||
link?: string;
|
||||
position?: string;
|
||||
display_order?: number;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
}
|
||||
): Promise<{ success: boolean; data: { banner: Banner }; message: string }> => {
|
||||
const response = await apiClient.post('/banners', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update banner
|
||||
*/
|
||||
export const updateBanner = async (
|
||||
id: number,
|
||||
data: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image_url?: string;
|
||||
link?: string;
|
||||
position?: string;
|
||||
display_order?: number;
|
||||
is_active?: boolean;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
}
|
||||
): Promise<{ success: boolean; data: { banner: Banner }; message: string }> => {
|
||||
const response = await apiClient.put(`/banners/${id}`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete banner
|
||||
*/
|
||||
export const deleteBanner = async (
|
||||
id: number
|
||||
): Promise<{ success: boolean; message: string }> => {
|
||||
const response = await apiClient.delete(`/banners/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload banner image
|
||||
*/
|
||||
export const uploadBannerImage = async (
|
||||
file: File
|
||||
): Promise<{ success: boolean; data: { image_url: string; full_url: string }; message: string }> => {
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
const response = await apiClient.post('/banners/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export interface BannerService {
|
||||
getBannersByPosition: typeof getBannersByPosition;
|
||||
getActiveBanners: typeof getActiveBanners;
|
||||
getAllBanners: typeof getAllBanners;
|
||||
getBannerById: typeof getBannerById;
|
||||
createBanner: typeof createBanner;
|
||||
updateBanner: typeof updateBanner;
|
||||
deleteBanner: typeof deleteBanner;
|
||||
uploadBannerImage: typeof uploadBannerImage;
|
||||
}
|
||||
|
||||
const bannerService: BannerService = {
|
||||
getBannersByPosition,
|
||||
getActiveBanners,
|
||||
getAllBanners,
|
||||
getBannerById,
|
||||
createBanner,
|
||||
updateBanner,
|
||||
deleteBanner,
|
||||
uploadBannerImage,
|
||||
};
|
||||
|
||||
export default bannerService as BannerService;
|
||||
|
||||
Reference in New Issue
Block a user