This commit is contained in:
Iliyan Angelov
2025-11-30 22:43:09 +02:00
parent 24b40450dd
commit 39fcfff811
1610 changed files with 5442 additions and 1383 deletions

View File

@@ -0,0 +1,62 @@
import apiClient from '../../../shared/services/apiClient';
export type CookiePolicySettings = {
analytics_enabled: boolean;
marketing_enabled: boolean;
preferences_enabled: boolean;
};
export type CookiePolicySettingsResponse = {
status: string;
data: CookiePolicySettings;
updated_at?: string;
updated_by?: string | null;
};
export type CookieIntegrationSettings = {
ga_measurement_id?: string | null;
fb_pixel_id?: string | null;
};
export type CookieIntegrationSettingsResponse = {
status: string;
data: CookieIntegrationSettings;
updated_at?: string;
updated_by?: string | null;
};
const adminPrivacyService = {
getCookiePolicy: async (): Promise<CookiePolicySettingsResponse> => {
const res = await apiClient.get<CookiePolicySettingsResponse>(
'/admin/privacy/cookie-policy'
);
return res.data;
},
updateCookiePolicy: async (
payload: CookiePolicySettings
): Promise<CookiePolicySettingsResponse> => {
const res = await apiClient.put<CookiePolicySettingsResponse>(
'/admin/privacy/cookie-policy',
payload
);
return res.data;
},
getIntegrations: async (): Promise<CookieIntegrationSettingsResponse> => {
const res = await apiClient.get<CookieIntegrationSettingsResponse>(
'/admin/privacy/integrations'
);
return res.data;
},
updateIntegrations: async (
payload: CookieIntegrationSettings
): Promise<CookieIntegrationSettingsResponse> => {
const res = await apiClient.put<CookieIntegrationSettingsResponse>(
'/admin/privacy/integrations',
payload
);
return res.data;
},
};
export default adminPrivacyService;

View File

@@ -0,0 +1,215 @@
import apiClient from '../../../shared/services/apiClient';
export interface Banner {
id: number;
title: string;
description?: string;
image_url: string;
link_url?: 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;
}
type CacheEntry = {
timestamp: number;
response: BannerListResponse;
};
const BANNER_CACHE_TTL_MS = 5 * 60 * 1000;
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 {
}
};
const getCachedOrFetch = async (
key: string,
fetcher: () => Promise<BannerListResponse>
): Promise<BannerListResponse> => {
const inMemory = memoryCache.get(key);
if (isCacheValid(inMemory)) {
return inMemory!.response;
}
const fromStorage = loadFromStorage(key);
if (fromStorage) {
memoryCache.set(key, fromStorage);
return fromStorage.response;
}
const response = await fetcher();
const entry: CacheEntry = {
timestamp: Date.now(),
response,
};
memoryCache.set(key, entry);
saveToStorage(key, entry);
return response;
};
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;
});
};
export const getActiveBanners = async ():
Promise<BannerListResponse> => {
const key = getCacheKey();
return getCachedOrFetch(key, async () => {
const response = await apiClient.get('/banners');
return response.data;
});
};
export const getAllBanners = async (
params?: { position?: string; page?: number; limit?: number }
): Promise<BannerListResponse> => {
const response = await apiClient.get('/banners', { params });
return response.data;
};
export const getBannerById = async (
id: number
): Promise<{ success: boolean; data: { banner: Banner } }> => {
const response = await apiClient.get(`/banners/${id}`);
return response.data;
};
export const createBanner = async (
data: {
title: string;
description?: string;
image_url: string;
link?: string;
link_url?: string;
position?: string;
display_order?: number;
start_date?: string;
end_date?: string;
}
): Promise<{ success: boolean; data: { banner: Banner }; message: string }> => {
const requestData = {
...data,
link: data.link_url || data.link,
};
delete requestData.link_url;
const response = await apiClient.post('/banners', requestData);
return response.data;
};
export const updateBanner = async (
id: number,
data: {
title?: string;
description?: string;
image_url?: string;
link?: string;
link_url?: string;
position?: string;
display_order?: number;
is_active?: boolean;
start_date?: string;
end_date?: string;
}
): Promise<{ success: boolean; data: { banner: Banner }; message: string }> => {
const requestData = {
...data,
link: data.link_url || data.link,
};
delete requestData.link_url;
const response = await apiClient.put(`/banners/${id}`, requestData);
return response.data;
};
export const deleteBanner = async (
id: number
): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.delete(`/banners/${id}`);
return response.data;
};
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);
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;

View File

@@ -0,0 +1,150 @@
import apiClient from '../../../shared/services/apiClient';
export interface BlogSection {
type: 'hero' | 'text' | 'image' | 'gallery' | 'quote' | 'features' | 'cta' | 'video';
title?: string;
content?: string;
image?: string;
images?: string[];
quote?: string;
author?: string;
features?: Array<{ title: string; description: string; icon?: string }>;
cta_text?: string;
cta_link?: string;
video_url?: string;
alignment?: 'left' | 'center' | 'right';
background_color?: string;
text_color?: string;
is_visible?: boolean;
}
export interface BlogPost {
id: number;
title: string;
slug: string;
excerpt?: string;
content?: string;
featured_image?: string;
author_id: number;
author_name?: string;
published_at?: string;
is_published?: boolean;
views_count: number;
tags?: string[];
meta_title?: string;
meta_description?: string;
meta_keywords?: string;
sections?: BlogSection[];
created_at: string;
updated_at: string;
}
export interface BlogPostCreate {
title: string;
slug?: string;
excerpt?: string;
content: string;
featured_image?: string;
tags?: string[];
meta_title?: string;
meta_description?: string;
meta_keywords?: string;
is_published?: boolean;
published_at?: string;
sections?: BlogSection[];
}
export interface BlogPostUpdate {
title?: string;
slug?: string;
excerpt?: string;
content?: string;
featured_image?: string;
tags?: string[];
meta_title?: string;
meta_description?: string;
meta_keywords?: string;
is_published?: boolean;
published_at?: string;
sections?: BlogSection[];
}
export interface BlogListResponse {
posts: BlogPost[];
pagination: {
page: number;
limit: number;
total: number;
pages: number;
};
all_tags?: string[];
}
export interface BlogPostResponse {
post: BlogPost;
}
class BlogService {
// Public endpoints
async getBlogPosts(params?: {
page?: number;
limit?: number;
search?: string;
tag?: string;
published_only?: boolean;
}): Promise<{ status: string; data: BlogListResponse }> {
const response = await apiClient.get('/blog/', { params });
return response.data;
}
async getBlogPostBySlug(slug: string): Promise<{ status: string; data: BlogPostResponse }> {
const response = await apiClient.get(`/blog/${slug}`);
return response.data;
}
// Admin endpoints
async getAllBlogPosts(params?: {
page?: number;
limit?: number;
search?: string;
published?: boolean;
}): Promise<{ status: string; data: BlogListResponse }> {
const response = await apiClient.get('/blog/admin/posts', { params });
return response.data;
}
async getBlogPostById(id: number): Promise<{ status: string; data: BlogPostResponse }> {
const response = await apiClient.get(`/blog/admin/posts/${id}`);
return response.data;
}
async createBlogPost(data: BlogPostCreate): Promise<{ status: string; data: BlogPostResponse; message?: string }> {
const response = await apiClient.post('/blog/admin/posts', data);
return response.data;
}
async updateBlogPost(id: number, data: BlogPostUpdate): Promise<{ status: string; data: BlogPostResponse; message?: string }> {
const response = await apiClient.put(`/blog/admin/posts/${id}`, data);
return response.data;
}
async deleteBlogPost(id: number): Promise<{ status: string; message?: string }> {
const response = await apiClient.delete(`/blog/admin/posts/${id}`);
return response.data;
}
async uploadBlogImage(file: File): Promise<{ status: string; data: { image_url: string; full_url: string }; message?: string }> {
const formData = new FormData();
formData.append('image', file);
const response = await apiClient.post('/blog/admin/upload-image', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
}
}
export const blogService = new BlogService();
export default blogService;

View File

@@ -0,0 +1,26 @@
import apiClient from '../../../shared/services/apiClient';
export interface ContactFormData {
name: string;
email: string;
subject: string;
message: string;
phone?: string;
}
export interface ContactResponse {
status: string;
message: string;
}
export const submitContactForm = async (
formData: ContactFormData
): Promise<ContactResponse> => {
const response = await apiClient.post('/contact/submit', formData);
return response.data;
};
export default {
submitContactForm,
};

View File

@@ -0,0 +1,377 @@
import apiClient from '../../../shared/services/apiClient';
export type PageType = 'home' | 'contact' | 'about' | 'footer' | 'seo' | 'privacy' | 'terms' | 'refunds' | 'cancellation' | 'accessibility' | 'faq';
export interface PageContent {
id?: number;
page_type: PageType;
title?: string;
subtitle?: string;
description?: string;
content?: string;
meta_title?: string;
meta_description?: string;
meta_keywords?: string;
og_title?: string;
og_description?: string;
og_image?: string;
canonical_url?: string;
contact_info?: {
phone?: string;
email?: string;
address?: string;
};
map_url?: string;
social_links?: {
facebook?: string;
twitter?: string;
instagram?: string;
linkedin?: string;
youtube?: string;
};
footer_links?: {
quick_links?: Array<{ label: string; url: string }>;
support_links?: Array<{ label: string; url: string }>;
};
badges?: Array<{ text: string; icon: string }>;
copyright_text?: string;
hero_title?: string;
hero_subtitle?: string;
hero_image?: string;
story_content?: string;
values?: Array<{ icon?: string; title: string; description: string }>;
features?: Array<{ icon?: string; title: string; description: string; image?: string }>;
about_hero_image?: string;
mission?: string;
vision?: string;
team?: Array<{ name: string; role: string; image?: string; bio?: string; social_links?: { linkedin?: string; twitter?: string; email?: string } }>;
timeline?: Array<{ year: string; title: string; description: string; image?: string }>;
achievements?: Array<{ icon?: string; title: string; description: string; year?: string; image?: string }>;
luxury_section_title?: string;
luxury_section_subtitle?: string;
luxury_section_image?: string;
luxury_features?: Array<{ icon?: string; title: string; description: string }>;
luxury_gallery_section_title?: string;
luxury_gallery_section_subtitle?: string;
luxury_gallery?: Array<string>;
luxury_testimonials_section_title?: string;
luxury_testimonials_section_subtitle?: string;
luxury_testimonials?: Array<{ name: string; title?: string; quote: string; image?: string }>;
amenities_section_title?: string;
amenities_section_subtitle?: string;
amenities?: Array<{ icon?: string; title: string; description: string; image?: string }>;
testimonials_section_title?: string;
testimonials_section_subtitle?: string;
testimonials?: Array<{ name: string; role: string; image?: string; rating: number; comment: string }>;
gallery_section_title?: string;
gallery_section_subtitle?: string;
gallery_images?: Array<string>;
about_preview_title?: string;
about_preview_subtitle?: string;
about_preview_content?: string;
about_preview_image?: string;
stats?: Array<{ number: string; label: string; icon?: string }>;
luxury_services_section_title?: string;
luxury_services_section_subtitle?: string;
luxury_services?: Array<{ icon?: string; title: string; description: string; image?: string }>;
luxury_experiences_section_title?: string;
luxury_experiences_section_subtitle?: string;
luxury_experiences?: Array<{ icon?: string; title: string; description: string; image?: string }>;
awards_section_title?: string;
awards_section_subtitle?: string;
awards?: Array<{ icon?: string; title: string; description: string; image?: string; year?: string }>;
cta_title?: string;
cta_subtitle?: string;
cta_button_text?: string;
cta_button_link?: string;
cta_image?: string;
partners_section_title?: string;
partners_section_subtitle?: string;
partners?: Array<{ name: string; logo: string; link?: string }>;
is_active?: boolean;
created_at?: string;
updated_at?: string;
}
export interface PageContentResponse {
status: string;
data: {
page_content?: PageContent | null;
page_contents?: PageContent[];
};
message?: string;
}
export interface UpdatePageContentData {
title?: string;
subtitle?: string;
description?: string;
content?: string;
meta_title?: string;
meta_description?: string;
meta_keywords?: string;
og_title?: string;
og_description?: string;
og_image?: string;
canonical_url?: string;
contact_info?: {
phone?: string;
email?: string;
address?: string;
};
map_url?: string;
social_links?: {
facebook?: string;
twitter?: string;
instagram?: string;
linkedin?: string;
youtube?: string;
};
footer_links?: {
quick_links?: Array<{ label: string; url: string }>;
support_links?: Array<{ label: string; url: string }>;
};
badges?: Array<{ text: string; icon: string }>;
copyright_text?: string;
hero_title?: string;
hero_subtitle?: string;
hero_image?: string;
story_content?: string;
values?: Array<{ icon?: string; title: string; description: string }>;
features?: Array<{ icon?: string; title: string; description: string; image?: string }>;
about_hero_image?: string;
mission?: string;
vision?: string;
team?: Array<{ name: string; role: string; image?: string; bio?: string; social_links?: { linkedin?: string; twitter?: string; email?: string } }>;
timeline?: Array<{ year: string; title: string; description: string; image?: string }>;
achievements?: Array<{ icon?: string; title: string; description: string; year?: string; image?: string }>;
luxury_section_title?: string;
luxury_section_subtitle?: string;
luxury_section_image?: string;
luxury_features?: Array<{ icon?: string; title: string; description: string }>;
luxury_gallery_section_title?: string;
luxury_gallery_section_subtitle?: string;
luxury_gallery?: Array<string>;
luxury_testimonials_section_title?: string;
luxury_testimonials_section_subtitle?: string;
luxury_testimonials?: Array<{ name: string; title?: string; quote: string; image?: string }>;
amenities_section_title?: string;
amenities_section_subtitle?: string;
amenities?: Array<{ icon?: string; title: string; description: string; image?: string }>;
testimonials_section_title?: string;
testimonials_section_subtitle?: string;
testimonials?: Array<{ name: string; role: string; image?: string; rating: number; comment: string }>;
gallery_section_title?: string;
gallery_section_subtitle?: string;
gallery_images?: Array<string>;
about_preview_title?: string;
about_preview_subtitle?: string;
about_preview_content?: string;
about_preview_image?: string;
stats?: Array<{ number: string; label: string; icon?: string }>;
luxury_services_section_title?: string;
luxury_services_section_subtitle?: string;
luxury_services?: Array<{ icon?: string; title: string; description: string; image?: string }>;
luxury_experiences_section_title?: string;
luxury_experiences_section_subtitle?: string;
luxury_experiences?: Array<{ icon?: string; title: string; description: string; image?: string }>;
awards_section_title?: string;
awards_section_subtitle?: string;
awards?: Array<{ icon?: string; title: string; description: string; image?: string; year?: string }>;
cta_title?: string;
cta_subtitle?: string;
cta_button_text?: string;
cta_button_link?: string;
cta_image?: string;
partners_section_title?: string;
partners_section_subtitle?: string;
partners?: Array<{ name: string; logo: string; link?: string }>;
is_active?: boolean;
}
const pageContentService = {
getAllPageContents: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/page-content');
return response.data;
},
getPageContent: async (pageType: PageType): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>(`/page-content/${pageType}`);
return response.data;
},
getHomeContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/home');
return response.data;
},
getAboutContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/about');
return response.data;
},
getContactContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/contact-content');
return response.data;
},
getFooterContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/footer');
return response.data;
},
getPrivacyContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/privacy');
return response.data;
},
getTermsContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/terms');
return response.data;
},
getRefundsContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/refunds');
return response.data;
},
getCancellationContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/cancellation');
return response.data;
},
getAccessibilityContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/accessibility');
return response.data;
},
getFAQContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/faq');
return response.data;
},
updatePageContent: async (
pageType: PageType,
data: UpdatePageContentData
): Promise<PageContentResponse> => {
const updateData: any = { ...data };
if (data.contact_info) {
const contactInfo = {
phone: data.contact_info.phone || '',
email: data.contact_info.email || '',
address: data.contact_info.address || '',
};
updateData.contact_info = contactInfo;
}
if (data.social_links) {
const socialLinks = {
facebook: data.social_links.facebook || '',
twitter: data.social_links.twitter || '',
instagram: data.social_links.instagram || '',
linkedin: data.social_links.linkedin || '',
youtube: data.social_links.youtube || '',
};
updateData.social_links = socialLinks;
}
if (data.footer_links) {
updateData.footer_links = {
quick_links: data.footer_links.quick_links || [],
support_links: data.footer_links.support_links || [],
};
}
if (data.badges) {
updateData.badges = data.badges;
}
if (data.values) {
updateData.values = data.values;
}
if (data.features) {
updateData.features = data.features;
}
if (data.luxury_features) {
updateData.luxury_features = data.luxury_features;
}
if (data.luxury_gallery) {
updateData.luxury_gallery = data.luxury_gallery;
}
if (data.luxury_testimonials) {
updateData.luxury_testimonials = data.luxury_testimonials;
}
if (data.amenities) {
updateData.amenities = data.amenities;
}
if (data.testimonials) {
updateData.testimonials = data.testimonials;
}
if (data.gallery_images) {
updateData.gallery_images = data.gallery_images;
}
if (data.stats) {
updateData.stats = data.stats;
}
if (data.luxury_services) {
updateData.luxury_services = data.luxury_services;
}
if (data.luxury_experiences) {
updateData.luxury_experiences = data.luxury_experiences;
}
if (data.awards) {
updateData.awards = data.awards;
}
if (data.partners) {
updateData.partners = data.partners;
}
if (data.team) {
updateData.team = data.team;
}
if (data.timeline) {
updateData.timeline = data.timeline;
}
if (data.achievements) {
updateData.achievements = data.achievements;
}
const response = await apiClient.put<PageContentResponse>(
`/page-content/${pageType}`,
updateData
);
return response.data;
},
uploadImage: 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('/page-content/upload', formData);
return response.data;
},
};
export default pageContentService;

View File

@@ -0,0 +1,75 @@
import apiClient from '../../../shared/services/apiClient';
export type CookieCategoryPreferences = {
necessary: boolean;
analytics: boolean;
marketing: boolean;
preferences: boolean;
};
export type CookieConsent = {
version: number;
updated_at: string;
has_decided: boolean;
categories: CookieCategoryPreferences;
};
export type CookieConsentResponse = {
status: string;
data: CookieConsent;
};
export type UpdateCookieConsentRequest = {
analytics?: boolean;
marketing?: boolean;
preferences?: boolean;
};
export type PublicPrivacyConfig = {
policy: {
analytics_enabled: boolean;
marketing_enabled: boolean;
preferences_enabled: boolean;
};
integrations: {
ga_measurement_id?: string | null;
fb_pixel_id?: string | null;
};
};
export type PublicPrivacyConfigResponse = {
status: string;
data: PublicPrivacyConfig;
};
const privacyService = {
getCookieConsent: async (): Promise<CookieConsent> => {
const response = await apiClient.get<CookieConsentResponse>(
'/privacy/cookie-consent'
);
return response.data.data;
},
getPublicConfig: async (): Promise<PublicPrivacyConfig> => {
const response = await apiClient.get<PublicPrivacyConfigResponse>(
'/privacy/config'
);
return response.data.data;
},
updateCookieConsent: async (
payload: UpdateCookieConsentRequest
): Promise<CookieConsent> => {
const response = await apiClient.post<CookieConsentResponse>(
'/privacy/cookie-consent',
payload
);
return response.data.data;
},
};
export default privacyService;