241 lines
5.6 KiB
TypeScript
241 lines
5.6 KiB
TypeScript
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 {
|
|
id: number;
|
|
title: string;
|
|
image_url: string;
|
|
link?: string;
|
|
position: string;
|
|
display_order: number;
|
|
is_active: boolean;
|
|
start_date?: string;
|
|
end_date?: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface BannerListResponse {
|
|
success: boolean;
|
|
status?: string;
|
|
data: {
|
|
banners: Banner[];
|
|
};
|
|
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 key = getCacheKey(position);
|
|
return getCachedOrFetch(key, async () => {
|
|
const response = await apiClient.get('/banners', {
|
|
params: { position },
|
|
});
|
|
return response.data;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get all active banners
|
|
*
|
|
* Cached on the client for a short TTL.
|
|
*/
|
|
export const getActiveBanners = async ():
|
|
Promise<BannerListResponse> => {
|
|
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;
|
|
};
|
|
|
|
/**
|
|
* 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;
|