import { API_CONFIG, getApiHeaders } from '../config/api'; // Types for Service API export interface ServiceFeature { id: number; title: string; description: string; icon: string; display_order: number; } export interface ServiceExpertise { id: number; title: string; description: string; icon: string; display_order: number; } export interface ServiceCategory { id: number; name: string; slug: string; description: string; display_order: number; } export interface Service { id: number; title: string; description: string; short_description?: string; slug: string; icon: string; image?: File | string; image_url?: string; price: string; formatted_price: string; category?: ServiceCategory; duration?: string; deliverables?: string; technologies?: string; process_steps?: string; features_description?: string; deliverables_description?: string; process_description?: string; why_choose_description?: string; expertise_description?: string; featured: boolean; display_order: number; is_active: boolean; created_at: string; updated_at: string; features?: ServiceFeature[]; expertise_items?: ServiceExpertise[]; } export interface ServiceListResponse { count: number; next: string | null; previous: string | null; results: Service[]; } export interface ServiceStats { total_services: number; featured_services: number; categories: number; average_price: number; } export interface ServiceSearchResponse { query: string; count: number; results: Service[]; } // Helper function to build query string const buildQueryString = (params: Record): string => { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { searchParams.append(key, value.toString()); } }); return searchParams.toString(); }; // Service API functions export const serviceService = { // Get all services with optional filtering getServices: async (params?: { featured?: boolean; category?: string; min_price?: number; max_price?: number; search?: string; ordering?: string; page?: number; }): Promise => { try { const queryString = params ? buildQueryString(params) : ''; const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES}/${queryString ? `?${queryString}` : ''}`; const response = await fetch(url, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch services: ${error.message}`); } throw new Error('Failed to fetch services: Unknown error'); } }, // Get a single service by slug getServiceBySlug: async (slug: string): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES}/${slug}/`; const response = await fetch(url, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch service: ${error.message}`); } throw new Error('Failed to fetch service: Unknown error'); } }, // Get featured services getFeaturedServices: async (): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_FEATURED}/`; const response = await fetch(url, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch featured services: ${error.message}`); } throw new Error('Failed to fetch featured services: Unknown error'); } }, // Search services searchServices: async (query: string): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_SEARCH}/?q=${encodeURIComponent(query)}`; const response = await fetch(url, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to search services: ${error.message}`); } throw new Error('Failed to search services: Unknown error'); } }, // Get service statistics getServiceStats: async (): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_STATS}/`; const response = await fetch(url, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch service stats: ${error.message}`); } throw new Error('Failed to fetch service stats: Unknown error'); } }, // Get all service categories getCategories: async (): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_CATEGORIES}/`; const response = await fetch(url, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch categories: ${error.message}`); } throw new Error('Failed to fetch categories: Unknown error'); } }, // Get a single category by slug getCategoryBySlug: async (slug: string): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_CATEGORIES}/${slug}/`; const response = await fetch(url, { method: 'GET', headers: getApiHeaders(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch category: ${error.message}`); } throw new Error('Failed to fetch category: Unknown error'); } }, // Admin functions (require authentication) createService: async (serviceData: Partial): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES}/admin/create/`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(serviceData), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to create service: ${error.message}`); } throw new Error('Failed to create service: Unknown error'); } }, updateService: async (slug: string, serviceData: Partial): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES}/admin/${slug}/update/`; const response = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(serviceData), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to update service: ${error.message}`); } throw new Error('Failed to update service: Unknown error'); } }, deleteService: async (slug: string): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES}/admin/${slug}/delete/`; const response = await fetch(url, { method: 'DELETE', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } } catch (error) { if (error instanceof Error) { throw new Error(`Failed to delete service: ${error.message}`); } throw new Error('Failed to delete service: Unknown error'); } }, // Upload image for a service uploadServiceImage: async (slug: string, imageFile: File): Promise => { try { const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES}/admin/${slug}/upload-image/`; const formData = new FormData(); formData.append('image', imageFile); const response = await fetch(url, { method: 'POST', body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || errorData.detail || `HTTP error! status: ${response.status}` ); } const result = await response.json(); return result.service; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to upload service image: ${error.message}`); } throw new Error('Failed to upload service image: Unknown error'); } }, }; // Utility functions export const serviceUtils = { // Format price for display formatPrice: (price: string | number): string => { const numPrice = typeof price === 'string' ? parseFloat(price) : price; return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(numPrice); }, // Get service image URL // Use relative URLs for same-domain images (Next.js can optimize via rewrites) // Use absolute URLs only for external images getServiceImageUrl: (service: Service): string => { // If service has an uploaded image if (service.image && typeof service.image === 'string' && service.image.startsWith('/media/')) { // Use relative URL - Next.js rewrite will handle fetching from backend during optimization return service.image; } // If service has an image_url if (service.image_url) { if (service.image_url.startsWith('http')) { // External URL - keep as absolute return service.image_url; } if (service.image_url.startsWith('/media/')) { // Same domain media - use relative URL return service.image_url; } // Other relative URLs return service.image_url; } // Fallback to default image (relative is fine for public images) return '/images/service/default.png'; }, // Generate service slug from title generateSlug: (title: string): string => { return title .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .trim(); }, // Check if service is featured isFeatured: (service: Service): boolean => { return service.featured; }, // Sort services by display order sortByDisplayOrder: (services: Service[]): Service[] => { return [...services].sort((a, b) => a.display_order - b.display_order); }, // Filter services by category filterByCategory: (services: Service[], categorySlug: string): Service[] => { return services.filter(service => service.category?.slug === categorySlug); }, // Get services within price range filterByPriceRange: (services: Service[], minPrice: number, maxPrice: number): Service[] => { return services.filter(service => { const price = parseFloat(service.price); return price >= minPrice && price <= maxPrice; }); }, };