This commit is contained in:
Iliyan Angelov
2025-11-20 21:06:30 +02:00
parent 44e11520c5
commit a38ab4fa82
77 changed files with 7169 additions and 360 deletions

View File

@@ -62,6 +62,11 @@ apiClient.interceptors.request.use(
config.url = config.url.replace(/\/\/+/, '/');
}
// Handle FormData - remove Content-Type header to let browser set it with boundary
if (config.data instanceof FormData) {
delete config.headers['Content-Type'];
}
// Add authorization token
const token = localStorage.getItem('token');
if (token) {

View File

@@ -212,14 +212,10 @@ const authService = {
const formData = new FormData();
formData.append('image', file);
// Don't set Content-Type header - let the browser set it with the correct boundary
const response = await apiClient.post<AuthResponse>(
'/api/auth/avatar/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
formData
);
return response.data;
},

View File

@@ -10,8 +10,9 @@ import apiClient from './apiClient';
export interface Banner {
id: number;
title: string;
description?: string;
image_url: string;
link?: string;
link_url?: string;
position: string;
display_order: number;
is_active: boolean;
@@ -157,13 +158,20 @@ export const createBanner = async (
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 response = await apiClient.post('/banners', data);
// Map link_url to link for backend compatibility
const requestData = {
...data,
link: data.link_url || data.link,
};
delete requestData.link_url;
const response = await apiClient.post('/banners', requestData);
return response.data;
};
@@ -177,6 +185,7 @@ export const updateBanner = async (
description?: string;
image_url?: string;
link?: string;
link_url?: string;
position?: string;
display_order?: number;
is_active?: boolean;
@@ -184,7 +193,13 @@ export const updateBanner = async (
end_date?: string;
}
): Promise<{ success: boolean; data: { banner: Banner }; message: string }> => {
const response = await apiClient.put(`/banners/${id}`, data);
// Map link_url to link for backend compatibility
const requestData = {
...data,
link: data.link_url || data.link,
};
delete requestData.link_url;
const response = await apiClient.put(`/banners/${id}`, requestData);
return response.data;
};
@@ -207,11 +222,8 @@ export const uploadBannerImage = async (
const formData = new FormData();
formData.append('image', file);
const response = await apiClient.post('/banners/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
// Don't set Content-Type header - let the browser set it with the correct boundary
const response = await apiClient.post('/banners/upload', formData);
return response.data;
};

View File

@@ -18,6 +18,7 @@ export interface BookingData {
service_id: number;
quantity: number;
}>;
promotion_code?: string;
}
export interface Booking {
@@ -205,6 +206,8 @@ export const getAllBookings = async (
search?: string;
page?: number;
limit?: number;
startDate?: string;
endDate?: string;
}
): Promise<BookingsResponse> => {
const response = await apiClient.get<BookingsResponse>('/bookings', { params });
@@ -286,14 +289,10 @@ export const notifyPayment = async (
formData.append('receipt', file);
}
// Don't set Content-Type header - let the browser set it with the correct boundary
const response = await apiClient.post(
'/notify/payment',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
formData
);
return response.data;

View File

@@ -42,6 +42,8 @@ export interface Invoice {
notes?: string;
terms_and_conditions?: string;
payment_instructions?: string;
is_proforma?: boolean;
promotion_code?: string;
items: InvoiceItem[];
created_at: string;
updated_at: string;

View File

@@ -34,12 +34,61 @@ export interface PageContent {
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 }>;
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 }>;
// Home page luxury sections
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;
@@ -84,12 +133,61 @@ export interface UpdatePageContentData {
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 }>;
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 }>;
// Home page luxury sections
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;
}
@@ -103,13 +201,45 @@ const pageContentService = {
},
/**
* Get content for a specific page
* Get content for a specific page (legacy method - kept for backward compatibility)
*/
getPageContent: async (pageType: PageType): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>(`/page-content/${pageType}`);
return response.data;
},
/**
* Get homepage content
*/
getHomeContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/home');
return response.data;
},
/**
* Get about page content
*/
getAboutContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/about');
return response.data;
},
/**
* Get contact page content
*/
getContactContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/contact-content');
return response.data;
},
/**
* Get footer content
*/
getFooterContent: async (): Promise<PageContentResponse> => {
const response = await apiClient.get<PageContentResponse>('/footer');
return response.data;
},
/**
* Update page content
*/
@@ -162,6 +292,50 @@ const pageContentService = {
if (data.features) {
updateData.features = data.features; // Send as array, backend will convert to JSON
}
// Handle luxury content arrays
if (data.luxury_features) {
updateData.luxury_features = data.luxury_features; // Send as array, backend will convert to JSON
}
if (data.luxury_gallery) {
updateData.luxury_gallery = data.luxury_gallery; // Send as array, backend will convert to JSON
}
if (data.luxury_testimonials) {
updateData.luxury_testimonials = data.luxury_testimonials; // Send as array, backend will convert to JSON
}
if (data.amenities) {
updateData.amenities = data.amenities; // Send as array, backend will convert to JSON
}
if (data.testimonials) {
updateData.testimonials = data.testimonials; // Send as array, backend will convert to JSON
}
if (data.gallery_images) {
updateData.gallery_images = data.gallery_images; // Send as array, backend will convert to JSON
}
if (data.stats) {
updateData.stats = data.stats; // Send as array, backend will convert to JSON
}
if (data.luxury_services) {
updateData.luxury_services = data.luxury_services; // Send as array, backend will convert to JSON
}
if (data.luxury_experiences) {
updateData.luxury_experiences = data.luxury_experiences; // Send as array, backend will convert to JSON
}
if (data.awards) {
updateData.awards = data.awards; // Send as array, backend will convert to JSON
}
if (data.partners) {
updateData.partners = data.partners; // Send as array, backend will convert to JSON
}
if (data.team) {
updateData.team = data.team; // Send as array, backend will convert to JSON
}
if (data.timeline) {
updateData.timeline = data.timeline; // Send as array, backend will convert to JSON
}
if (data.achievements) {
updateData.achievements = data.achievements; // Send as array, backend will convert to JSON
}
const response = await apiClient.put<PageContentResponse>(
`/page-content/${pageType}`,
@@ -169,6 +343,20 @@ const pageContentService = {
);
return response.data;
},
/**
* Upload page content image
*/
uploadImage: async (
file: File
): Promise<{ success: boolean; data: { image_url: string; full_url: string }; message: string }> => {
const formData = new FormData();
formData.append('image', file);
// Don't set Content-Type header - let the browser set it with the correct boundary
const response = await apiClient.post('/page-content/upload', formData);
return response.data;
},
};
export default pageContentService;

View File

@@ -97,14 +97,10 @@ export const confirmBankTransfer = async (
formData.append('receipt', receipt);
}
// Don't set Content-Type header - let the browser set it with the correct boundary
const response = await apiClient.post(
'/payments/confirm',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
formData
);
return response.data;

View File

@@ -322,14 +322,10 @@ const systemSettingsService = {
const formData = new FormData();
formData.append('image', file);
// Don't set Content-Type header - let the browser set it with the correct boundary
const response = await apiClient.post<UploadLogoResponse>(
'/api/admin/system-settings/company/logo',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
formData
);
return response.data;
},
@@ -343,14 +339,10 @@ const systemSettingsService = {
const formData = new FormData();
formData.append('image', file);
// Don't set Content-Type header - let the browser set it with the correct boundary
const response = await apiClient.post<UploadFaviconResponse>(
'/api/admin/system-settings/company/favicon',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
formData
);
return response.data;
},