Files
Hotel-Booking/Frontend/src/services/api/bannerService.ts
Iliyan Angelov 48353cde9c update
2025-11-16 20:05:08 +02:00

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;