This commit is contained in:
Iliyan Angelov
2025-11-24 03:52:08 +02:00
parent dfcaebaf8c
commit 366f28677a
18241 changed files with 865352 additions and 567 deletions

View File

@@ -0,0 +1,325 @@
import { API_BASE_URL } from '../config/api';
// Types for About Us data
export interface AboutStat {
number: string;
label: string;
order: number;
}
export interface AboutSocialLink {
platform: string;
url: string;
icon: string;
aria_label: string;
order: number;
}
export interface AboutBanner {
id: number;
title: string;
subtitle: string;
description: string;
badge_text: string;
badge_icon: string;
cta_text: string;
cta_link: string;
cta_icon: string;
image_url: string | null;
is_active: boolean;
stats: AboutStat[];
social_links: AboutSocialLink[];
created_at: string;
updated_at: string;
}
export interface AboutFeature {
title: string;
description: string;
icon: string;
order: number;
}
export interface AboutService {
id: number;
title: string;
subtitle: string;
description: string;
badge_text: string;
badge_icon: string;
image_url: string | null;
cta_text: string;
cta_link: string;
is_active: boolean;
features: AboutFeature[];
created_at: string;
updated_at: string;
}
export interface AboutProcessStep {
step_number: string;
title: string;
description: string;
order: number;
}
export interface AboutProcess {
id: number;
title: string;
subtitle: string;
description: string;
badge_text: string;
badge_icon: string;
image_url: string | null;
cta_text: string;
cta_link: string;
is_active: boolean;
steps: AboutProcessStep[];
created_at: string;
updated_at: string;
}
export interface AboutMilestone {
year: string;
title: string;
description: string;
order: number;
}
export interface AboutJourney {
id: number;
title: string;
subtitle: string;
description: string;
badge_text: string;
badge_icon: string;
image_url: string | null;
cta_text: string;
cta_link: string;
is_active: boolean;
milestones: AboutMilestone[];
created_at: string;
updated_at: string;
}
export interface AboutPageData {
banner: AboutBanner;
service: AboutService;
process: AboutProcess;
journey: AboutJourney;
}
class AboutServiceAPI {
private baseUrl = `${API_BASE_URL}/api/about`;
/**
* Get all about page data in one request
*/
async getAboutPageData(): Promise<AboutPageData> {
try {
const response = await fetch(`${this.baseUrl}/page/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
/**
* Get all about banners
*/
async getBanners(): Promise<AboutBanner[]> {
try {
const response = await fetch(`${this.baseUrl}/banner/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Get a specific about banner by ID
*/
async getBanner(id: number): Promise<AboutBanner> {
try {
const response = await fetch(`${this.baseUrl}/banner/${id}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
/**
* Get all about services
*/
async getServices(): Promise<AboutService[]> {
try {
const response = await fetch(`${this.baseUrl}/service/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Get a specific about service by ID
*/
async getService(id: number): Promise<AboutService> {
try {
const response = await fetch(`${this.baseUrl}/service/${id}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
/**
* Get all about processes
*/
async getProcesses(): Promise<AboutProcess[]> {
try {
const response = await fetch(`${this.baseUrl}/process/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Get a specific about process by ID
*/
async getProcess(id: number): Promise<AboutProcess> {
try {
const response = await fetch(`${this.baseUrl}/process/${id}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
/**
* Get all about journeys
*/
async getJourneys(): Promise<AboutJourney[]> {
try {
const response = await fetch(`${this.baseUrl}/journey/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Get a specific about journey by ID
*/
async getJourney(id: number): Promise<AboutJourney> {
try {
const response = await fetch(`${this.baseUrl}/journey/${id}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
}
// Export a singleton instance
export const aboutService = new AboutServiceAPI();
export default aboutService;

View File

@@ -0,0 +1,422 @@
import { API_CONFIG } from '../config/api';
// Types for Blog API
export interface BlogAuthor {
id: number;
name: string;
email?: string;
bio?: string;
avatar?: string;
}
export interface BlogCategory {
id: number;
title: string;
slug: string;
description?: string;
display_order: number;
posts_count?: number;
}
export interface BlogTag {
id: number;
name: string;
slug: string;
}
export interface BlogPost {
id: number;
title: string;
slug: string;
content?: string;
excerpt: string;
thumbnail?: string;
featured_image?: string;
author?: BlogAuthor;
author_name?: string;
category?: BlogCategory;
category_title?: string;
category_slug?: string;
tags?: BlogTag[];
meta_description?: string;
meta_keywords?: string;
published: boolean;
featured: boolean;
views_count: number;
reading_time: number;
published_at: string;
created_at: string;
updated_at: string;
related_posts?: BlogPost[];
}
export interface BlogPostListResponse {
count: number;
next: string | null;
previous: string | null;
results: BlogPost[];
}
export interface BlogComment {
id: number;
post: number;
name: string;
email: string;
content: string;
parent?: number;
is_approved: boolean;
created_at: string;
updated_at: string;
replies?: BlogComment[];
}
export interface BlogCommentCreateData {
post: number;
name: string;
email: string;
content: string;
parent?: number;
}
// Helper function to build query string
const buildQueryString = (params: Record<string, any>): string => {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
searchParams.append(key, value.toString());
}
});
return searchParams.toString();
};
// Blog API functions
export const blogService = {
// Get all blog posts with optional filtering
getPosts: async (params?: {
category?: string;
tag?: string;
author?: number;
search?: string;
featured?: boolean;
ordering?: string;
page?: number;
page_size?: number;
}): Promise<BlogPostListResponse> => {
try {
const queryString = params ? buildQueryString(params) : '';
const url = `${API_CONFIG.BASE_URL}/api/blog/posts/${queryString ? `?${queryString}` : ''}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get a single blog post by slug
getPostBySlug: async (slug: string): Promise<BlogPost> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/posts/${slug}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get featured blog posts
getFeaturedPosts: async (): Promise<BlogPost[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/posts/featured/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get latest blog posts
getLatestPosts: async (limit: number = 5): Promise<BlogPost[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/posts/latest/?limit=${limit}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get popular blog posts
getPopularPosts: async (limit: number = 5): Promise<BlogPost[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/posts/popular/?limit=${limit}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get related posts for a specific post
getRelatedPosts: async (postSlug: string): Promise<BlogPost[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/posts/${postSlug}/related/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get all blog categories
getCategories: async (): Promise<BlogCategory[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/categories/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
throw error;
}
},
// Get categories with posts
getCategoriesWithPosts: async (): Promise<BlogCategory[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/categories/with_posts/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get a single category by slug
getCategoryBySlug: async (slug: string): Promise<BlogCategory> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/categories/${slug}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get all blog tags
getTags: async (): Promise<BlogTag[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/tags/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
throw error;
}
},
// Get posts by tag
getPostsByTag: async (tagSlug: string): Promise<BlogPostListResponse> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/tags/${tagSlug}/posts/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get all blog authors
getAuthors: async (): Promise<BlogAuthor[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/authors/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
throw error;
}
},
// Get posts by author
getPostsByAuthor: async (authorId: number): Promise<BlogPostListResponse> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/authors/${authorId}/posts/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get comments for a post
getComments: async (postId: number): Promise<BlogComment[]> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/comments/?post=${postId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return Array.isArray(data) ? data : data.results || [];
} catch (error) {
throw error;
}
},
// Create a new comment
createComment: async (commentData: BlogCommentCreateData): Promise<{ message: string; data: BlogComment }> => {
try {
const response = await fetch(`${API_CONFIG.BASE_URL}/api/blog/comments/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(commentData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
};

View File

@@ -0,0 +1,229 @@
import { API_BASE_URL } from '../config/api';
export interface JobPosition {
id: number;
title: string;
slug: string;
department: string;
employment_type: string;
location_type: string;
location: string;
open_positions: number;
experience_required?: string;
salary_min?: number;
salary_max?: number;
salary_currency: string;
salary_period: string;
salary_additional?: string;
short_description?: string;
about_role?: string;
requirements?: string[];
responsibilities?: string[];
qualifications?: string[];
bonus_points?: string[];
benefits?: string[];
start_date: string;
posted_date: string;
updated_date: string;
deadline?: string;
status: string;
featured?: boolean;
}
export interface JobApplication {
job: number;
first_name: string;
last_name: string;
email: string;
phone?: string;
current_position?: string;
current_company?: string;
years_of_experience?: string;
cover_letter?: string;
resume: File;
portfolio_url?: string;
linkedin_url?: string;
github_url?: string;
website_url?: string;
available_from?: string;
notice_period?: string;
expected_salary?: number;
salary_currency?: string;
consent: boolean;
}
class CareerService {
private baseUrl = `${API_BASE_URL}/api/career`;
/**
* Get all active job positions
*/
async getAllJobs(): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch jobs: ${response.statusText}`);
}
const data = await response.json();
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Get a single job position by slug
*/
async getJobBySlug(slug: string): Promise<JobPosition> {
try {
const response = await fetch(`${this.baseUrl}/jobs/${slug}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch job: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
/**
* Get featured job positions
*/
async getFeaturedJobs(): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs/featured`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch featured jobs: ${response.statusText}`);
}
const data = await response.json();
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Submit a job application
*/
async submitApplication(applicationData: JobApplication): Promise<any> {
try {
const formData = new FormData();
// Required fields
formData.append('job', applicationData.job.toString());
formData.append('first_name', applicationData.first_name);
formData.append('last_name', applicationData.last_name);
formData.append('email', applicationData.email);
formData.append('consent', applicationData.consent.toString());
formData.append('resume', applicationData.resume);
// Optional fields (only append if they exist)
if (applicationData.phone) formData.append('phone', applicationData.phone);
if (applicationData.current_position) formData.append('current_position', applicationData.current_position);
if (applicationData.current_company) formData.append('current_company', applicationData.current_company);
if (applicationData.years_of_experience) formData.append('years_of_experience', applicationData.years_of_experience);
if (applicationData.cover_letter) formData.append('cover_letter', applicationData.cover_letter);
if (applicationData.portfolio_url) formData.append('portfolio_url', applicationData.portfolio_url);
if (applicationData.linkedin_url) formData.append('linkedin_url', applicationData.linkedin_url);
if (applicationData.github_url) formData.append('github_url', applicationData.github_url);
if (applicationData.website_url) formData.append('website_url', applicationData.website_url);
if (applicationData.available_from) formData.append('available_from', applicationData.available_from);
if (applicationData.notice_period) formData.append('notice_period', applicationData.notice_period);
if (applicationData.expected_salary !== undefined) formData.append('expected_salary', applicationData.expected_salary.toString());
if (applicationData.salary_currency) formData.append('salary_currency', applicationData.salary_currency);
const response = await fetch(`${this.baseUrl}/applications`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage = errorData.error || errorData.message || errorData.detail || `HTTP ${response.status}: ${response.statusText}`;
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
/**
* Filter jobs by department
*/
async getJobsByDepartment(department: string): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs?department=${department}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch jobs: ${response.statusText}`);
}
const data = await response.json();
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Filter jobs by employment type
*/
async getJobsByEmploymentType(employmentType: string): Promise<JobPosition[]> {
try {
const response = await fetch(`${this.baseUrl}/jobs?employment_type=${employmentType}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch jobs: ${response.statusText}`);
}
const data = await response.json();
// Handle paginated response - extract results array
return data.results || data;
} catch (error) {
throw error;
}
}
}
export const careerService = new CareerService();

View File

@@ -0,0 +1,364 @@
import { API_CONFIG } from '../config/api';
// Types for Case Study API
export interface CaseStudyCategory {
id: number;
name: string;
slug: string;
description?: string;
display_order: number;
case_studies_count?: number;
}
export interface Client {
id: number;
name: string;
slug: string;
logo?: string;
description?: string;
website?: string;
}
export interface CaseStudyImage {
id: number;
image: string;
caption?: string;
display_order: number;
}
export interface CaseStudyProcess {
id: number;
title: string;
description: string;
step_number: number;
}
export interface CaseStudy {
id: number;
title: string;
slug: string;
subtitle?: string;
description?: string;
excerpt: string;
thumbnail?: string;
featured_image?: string;
poster_image?: string;
project_image?: string;
project_overview?: string;
site_map_content?: string;
category?: CaseStudyCategory;
category_name?: string;
category_slug?: string;
client?: Client;
client_name?: string;
gallery_images?: CaseStudyImage[];
process_steps?: CaseStudyProcess[];
meta_description?: string;
meta_keywords?: string;
published: boolean;
featured: boolean;
views_count: number;
display_order: number;
published_at: string;
created_at: string;
updated_at: string;
related_case_studies?: CaseStudy[];
}
export interface CaseStudyListResponse {
count: number;
next: string | null;
previous: string | null;
results: CaseStudy[];
}
// Helper function to build query string
const buildQueryString = (params: Record<string, any>): string => {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
searchParams.append(key, value.toString());
}
});
return searchParams.toString();
};
// Case Study API functions
export const caseStudyService = {
// Get all case studies with optional filtering
getCaseStudies: async (params?: {
category?: string;
client?: string;
search?: string;
featured?: boolean;
ordering?: string;
page?: number;
page_size?: number;
}): Promise<CaseStudyListResponse> => {
try {
const queryString = params ? buildQueryString(params) : '';
const url = queryString
? `${API_CONFIG.BASE_URL}/api/case-studies/case-studies/?${queryString}`
: `${API_CONFIG.BASE_URL}/api/case-studies/case-studies/`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get a single case study by slug
getCaseStudyBySlug: async (slug: string): Promise<CaseStudy> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/case-studies/${slug}/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get featured case studies
getFeaturedCaseStudies: async (): Promise<CaseStudy[]> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/case-studies/featured/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get latest case studies
getLatestCaseStudies: async (limit: number = 6): Promise<CaseStudy[]> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/case-studies/latest/?limit=${limit}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get popular case studies
getPopularCaseStudies: async (limit: number = 6): Promise<CaseStudy[]> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/case-studies/popular/?limit=${limit}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get related case studies for a specific case study
getRelatedCaseStudies: async (slug: string): Promise<CaseStudy[]> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/case-studies/${slug}/related/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get all categories
getCategories: async (): Promise<CaseStudyCategory[]> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/categories/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get categories with case studies
getCategoriesWithCaseStudies: async (): Promise<CaseStudyCategory[]> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/categories/with_case_studies/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get all clients
getClients: async (): Promise<Client[]> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/clients/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get a client by slug
getClientBySlug: async (slug: string): Promise<Client> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/clients/${slug}/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
// Get case studies for a specific client
getClientCaseStudies: async (slug: string): Promise<CaseStudyListResponse> => {
try {
const response = await fetch(
`${API_CONFIG.BASE_URL}/api/case-studies/clients/${slug}/case_studies/`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
},
};
export default caseStudyService;

View File

@@ -0,0 +1,131 @@
/**
* Contact API Service
* Handles communication with the Django REST API for contact form submissions
*/
import { API_CONFIG } from '@/lib/config/api';
export interface ContactFormData {
first_name: string;
last_name: string;
email: string;
phone?: string;
company: string;
job_title: string;
industry?: string;
company_size?: string;
project_type?: string;
timeline?: string;
budget?: string;
message: string;
newsletter_subscription: boolean;
privacy_consent: boolean;
}
export interface ContactSubmissionResponse {
message: string;
submission_id: number;
status: string;
}
export interface ApiError {
message: string;
errors?: Record<string, string[]>;
status: number;
}
class ContactApiService {
private baseUrl: string;
constructor() {
this.baseUrl = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONTACT}`;
}
/**
* Submit a contact form to the Django API
*/
async submitContactForm(data: ContactFormData): Promise<ContactSubmissionResponse> {
try {
const response = await fetch(`${this.baseUrl}/submissions/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
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;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to submit contact form: ${error.message}`);
}
throw new Error('Failed to submit contact form: Unknown error');
}
}
/**
* Get contact submission statistics (admin only)
*/
async getContactStats(): Promise<any> {
try {
const response = await fetch(`${this.baseUrl}/submissions/stats/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to fetch contact stats: ${error.message}`);
}
throw new Error('Failed to fetch contact stats: Unknown error');
}
}
/**
* Get recent contact submissions (admin only)
*/
async getRecentSubmissions(): Promise<any[]> {
try {
const response = await fetch(`${this.baseUrl}/submissions/recent/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to fetch recent submissions: ${error.message}`);
}
throw new Error('Failed to fetch recent submissions: Unknown error');
}
}
}
// Create and export a singleton instance
export const contactApiService = new ContactApiService();
// Export the class for testing purposes
export default ContactApiService;

View File

@@ -0,0 +1,115 @@
import { API_BASE_URL } from '../config/api';
export interface PolicySection {
id: number;
heading: string;
content: string;
order: number;
}
export interface Policy {
id: number;
type: 'privacy' | 'terms' | 'support';
title: string;
slug: string;
description: string;
last_updated: string;
version: string;
effective_date: string;
sections: PolicySection[];
}
export interface PolicyListItem {
id: number;
type: 'privacy' | 'terms' | 'support';
title: string;
slug: string;
description: string;
last_updated: string;
version: string;
}
class PolicyServiceAPI {
private baseUrl = `${API_BASE_URL}/api/policies`;
/**
* Get all policies
*/
async getPolicies(): Promise<PolicyListItem[]> {
try {
const response = await fetch(`${this.baseUrl}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.results || data;
} catch (error) {
throw error;
}
}
/**
* Get a specific policy by type
*/
async getPolicyByType(type: 'privacy' | 'terms' | 'support'): Promise<Policy> {
try {
const response = await fetch(`${this.baseUrl}/${type}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
/**
* Get a specific policy by ID
*/
async getPolicyById(id: number): Promise<Policy> {
try {
const response = await fetch(`${this.baseUrl}/${id}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
}
// Export a singleton instance
export const policyService = new PolicyServiceAPI();
// Export individual functions for convenience
export const getPolicies = () => policyService.getPolicies();
export const getPolicyByType = (type: 'privacy' | 'terms' | 'support') => policyService.getPolicyByType(type);
export const getPolicyById = (id: number) => policyService.getPolicyById(id);
export default policyService;

View File

@@ -0,0 +1,495 @@
import { API_CONFIG } 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, any>): 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<ServiceListResponse> => {
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: {
'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}`
);
}
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<Service> => {
try {
const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES}/${slug}/`;
const response = await fetch(url, {
method: 'GET',
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}`
);
}
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<ServiceListResponse> => {
try {
const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_FEATURED}/`;
const response = await fetch(url, {
method: 'GET',
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}`
);
}
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<ServiceSearchResponse> => {
try {
const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_SEARCH}/?q=${encodeURIComponent(query)}`;
const response = await fetch(url, {
method: 'GET',
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}`
);
}
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<ServiceStats> => {
try {
const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_STATS}/`;
const response = await fetch(url, {
method: 'GET',
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}`
);
}
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<ServiceCategory[]> => {
try {
const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_CATEGORIES}/`;
const response = await fetch(url, {
method: 'GET',
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}`
);
}
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<ServiceCategory> => {
try {
const url = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SERVICES_CATEGORIES}/${slug}/`;
const response = await fetch(url, {
method: 'GET',
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}`
);
}
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<Service>): Promise<Service> => {
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<Service>): Promise<Service> => {
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<void> => {
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<Service> => {
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
getServiceImageUrl: (service: Service): string => {
// If service has an uploaded image
if (service.image && typeof service.image === 'string' && service.image.startsWith('/media/')) {
return `${API_CONFIG.BASE_URL}${service.image}`;
}
// If service has an image_url
if (service.image_url) {
if (service.image_url.startsWith('http')) {
return service.image_url;
}
return `${API_CONFIG.BASE_URL}${service.image_url}`;
}
// Fallback to default image
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;
});
},
};

View File

@@ -0,0 +1,414 @@
import { API_CONFIG } from '../config/api';
const BASE_URL = `${API_CONFIG.BASE_URL}/api/support`;
// ==================== Types ====================
export interface TicketStatus {
id: number;
name: string;
color: string;
description: string;
is_closed: boolean;
display_order: number;
}
export interface TicketPriority {
id: number;
name: string;
level: number;
color: string;
description: string;
sla_hours: number;
}
export interface TicketCategory {
id: number;
name: string;
description: string;
color: string;
icon: string;
display_order: number;
}
export interface TicketMessage {
id: number;
ticket: number;
message_type: string;
content: string;
author_name: string;
author_email: string;
is_internal: boolean;
created_at: string;
updated_at: string;
attachments: string[];
is_read: boolean;
}
export interface TicketActivity {
id: number;
activity_type: string;
description: string;
user_name: string;
old_value: string;
new_value: string;
created_at: string;
}
export interface SupportTicket {
id: number;
ticket_number: string;
title: string;
description: string;
ticket_type: string;
user_name: string;
user_email: string;
user_phone: string;
company: string;
category: number | null;
category_name: string;
priority: number | null;
priority_name: string;
priority_color: string;
status: number | null;
status_name: string;
status_color: string;
assigned_to: number | null;
assigned_at: string | null;
created_at: string;
updated_at: string;
closed_at: string | null;
last_activity: string;
first_response_at: string | null;
sla_deadline: string | null;
tags: string;
is_escalated: boolean;
escalation_reason: string;
attachments: string[];
messages: TicketMessage[];
activities: TicketActivity[];
}
export interface CreateTicketData {
title: string;
description: string;
ticket_type: string;
user_name: string;
user_email: string;
user_phone?: string;
company?: string;
category?: number;
}
export interface KnowledgeBaseCategory {
id: number;
name: string;
slug: string;
description: string;
icon: string;
color: string;
display_order: number;
article_count: number;
}
export interface KnowledgeBaseArticle {
id: number;
title: string;
slug: string;
category: number;
category_name: string;
category_slug: string;
content?: string;
summary: string;
meta_description?: string;
keywords?: string;
is_featured: boolean;
view_count: number;
helpful_count: number;
not_helpful_count: number;
created_at: string;
updated_at: string;
published_at: string;
}
export interface SupportSettings {
id: number;
setting_name: string;
setting_value: string;
description: string;
is_active: boolean;
}
// ==================== API Functions ====================
/**
* Fetch all ticket categories
*/
export const getTicketCategories = async (): Promise<TicketCategory[]> => {
try {
const response = await fetch(`${BASE_URL}/categories/`);
if (!response.ok) throw new Error('Failed to fetch ticket categories');
const data = await response.json();
// Handle paginated response
return data.results || data;
} catch (error) {
throw error;
}
};
/**
* Fetch all ticket statuses
*/
export const getTicketStatuses = async (): Promise<TicketStatus[]> => {
try {
const response = await fetch(`${BASE_URL}/statuses/`);
if (!response.ok) throw new Error('Failed to fetch ticket statuses');
const data = await response.json();
// Handle paginated response
return data.results || data;
} catch (error) {
throw error;
}
};
/**
* Fetch all ticket priorities
*/
export const getTicketPriorities = async (): Promise<TicketPriority[]> => {
try {
const response = await fetch(`${BASE_URL}/priorities/`);
if (!response.ok) throw new Error('Failed to fetch ticket priorities');
const data = await response.json();
// Handle paginated response
return data.results || data;
} catch (error) {
throw error;
}
};
/**
* Create a new support ticket
*/
export const createTicket = async (data: CreateTicketData): Promise<SupportTicket> => {
try {
const response = await fetch(`${BASE_URL}/tickets/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
// Handle specific HTTP status codes
if (response.status === 400) {
// Handle validation errors
if (errorData.user_email) {
throw new Error(errorData.user_email[0] || 'Email validation failed');
}
if (errorData.title) {
throw new Error(errorData.title[0] || 'Title is required');
}
if (errorData.description) {
throw new Error(errorData.description[0] || 'Description is required');
}
if (errorData.user_name) {
throw new Error(errorData.user_name[0] || 'Name is required');
}
// Generic validation error
throw new Error(errorData.detail || errorData.message || 'Please check all required fields and try again');
} else if (response.status === 429) {
throw new Error('Too many requests. Please wait a moment and try again');
} else if (response.status >= 500) {
throw new Error('Server error. Please try again later');
} else if (response.status === 403) {
throw new Error('Access denied. Please contact support if this persists');
} else if (response.status === 404) {
throw new Error('Service not found. Please refresh the page and try again');
}
throw new Error(errorData.detail || errorData.message || 'An unexpected error occurred');
}
return await response.json();
} catch (error) {
throw error;
}
};
/**
* Check ticket status by ticket number
*/
export const checkTicketStatus = async (ticketNumber: string): Promise<SupportTicket> => {
try {
const response = await fetch(`${BASE_URL}/tickets/check-status/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ticket_number: ticketNumber }),
});
if (!response.ok) {
if (response.status === 404) {
throw new Error('Ticket not found');
}
throw new Error('Failed to check ticket status');
}
return await response.json();
} catch (error) {
throw error;
}
};
/**
* Add a message to a ticket
*/
export const addTicketMessage = async (
ticketId: number,
content: string,
authorName: string,
authorEmail: string
): Promise<TicketMessage> => {
try {
const response = await fetch(`${BASE_URL}/tickets/${ticketId}/add-message/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
content,
author_name: authorName,
author_email: authorEmail,
}),
});
if (!response.ok) throw new Error('Failed to add ticket message');
return await response.json();
} catch (error) {
throw error;
}
};
/**
* Fetch all knowledge base categories
*/
export const getKnowledgeBaseCategories = async (): Promise<KnowledgeBaseCategory[]> => {
try {
const response = await fetch(`${BASE_URL}/knowledge-base-categories/`);
if (!response.ok) throw new Error('Failed to fetch knowledge base categories');
const data = await response.json();
// Handle paginated response
return data.results || data;
} catch (error) {
throw error;
}
};
/**
* Fetch all knowledge base articles
*/
export const getKnowledgeBaseArticles = async (search?: string): Promise<KnowledgeBaseArticle[]> => {
try {
const url = search
? `${BASE_URL}/knowledge-base/?search=${encodeURIComponent(search)}`
: `${BASE_URL}/knowledge-base/`;
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch knowledge base articles');
const data = await response.json();
// Handle paginated response
return data.results || data;
} catch (error) {
throw error;
}
};
/**
* Fetch featured knowledge base articles
*/
export const getFeaturedArticles = async (): Promise<KnowledgeBaseArticle[]> => {
try {
const response = await fetch(`${BASE_URL}/knowledge-base/featured/`);
if (!response.ok) throw new Error('Failed to fetch featured articles');
const data = await response.json();
// Handle both array and paginated responses
return Array.isArray(data) ? data : (data.results || data);
} catch (error) {
throw error;
}
};
/**
* Fetch a single knowledge base article by slug
*/
export const getKnowledgeBaseArticle = async (slug: string): Promise<KnowledgeBaseArticle> => {
try {
const response = await fetch(`${BASE_URL}/knowledge-base/${slug}/`);
if (!response.ok) throw new Error('Failed to fetch knowledge base article');
return await response.json();
} catch (error) {
throw error;
}
};
/**
* Fetch articles by category
*/
export const getArticlesByCategory = async (categorySlug: string): Promise<KnowledgeBaseArticle[]> => {
try {
const response = await fetch(`${BASE_URL}/knowledge-base/by-category/${categorySlug}/`);
if (!response.ok) throw new Error('Failed to fetch articles by category');
const data = await response.json();
// Handle paginated response
return data.results || data;
} catch (error) {
throw error;
}
};
/**
* Mark an article as helpful or not helpful
*/
export const markArticleHelpful = async (slug: string, helpful: boolean): Promise<{ helpful_count: number; not_helpful_count: number }> => {
try {
const response = await fetch(`${BASE_URL}/knowledge-base/${slug}/mark-helpful/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ helpful }),
});
if (!response.ok) throw new Error('Failed to mark article helpful');
return await response.json();
} catch (error) {
throw error;
}
};
/**
* Fetch support settings
*/
export const getSupportSettings = async (): Promise<SupportSettings[]> => {
try {
const response = await fetch(`${BASE_URL}/settings/`);
if (!response.ok) throw new Error('Failed to fetch support settings');
const data = await response.json();
// Handle paginated response
return data.results || data;
} catch (error) {
throw error;
}
};
/**
* Fetch a specific support setting by name
*/
export const getSupportSetting = async (settingName: string): Promise<SupportSettings> => {
try {
const response = await fetch(`${BASE_URL}/settings/${settingName}/`);
if (!response.ok) throw new Error('Failed to fetch support setting');
return await response.json();
} catch (error) {
throw error;
}
};

View File

@@ -0,0 +1,46 @@
/**
* API Configuration
* Centralized configuration for API endpoints
*
* In Development: Calls backend directly at http://localhost:8000
* In Production: Uses Next.js rewrites/nginx proxy at /api (internal network only)
*/
// Production: Use relative URLs (nginx proxy)
// Development: Use full backend URL
const isProduction = process.env.NODE_ENV === 'production';
export const API_BASE_URL = isProduction
? '' // Use relative URLs in production (proxied by nginx)
: (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000');
export const API_CONFIG = {
// Django API Base URL
BASE_URL: API_BASE_URL,
// Media files URL (for uploaded images, etc.)
MEDIA_URL: `${API_BASE_URL}/media`,
// API Endpoints
ENDPOINTS: {
CONTACT: '/api/contact',
CONTACT_SUBMISSIONS: '/api/contact/submissions',
CONTACT_STATS: '/api/contact/submissions/stats',
CONTACT_RECENT: '/api/contact/submissions/recent',
SERVICES: '/api/services',
SERVICES_FEATURED: '/api/services/featured',
SERVICES_SEARCH: '/api/services/search',
SERVICES_STATS: '/api/services/stats',
SERVICES_CATEGORIES: '/api/services/categories',
},
// Request timeout (in milliseconds)
TIMEOUT: 10000,
// Retry configuration
RETRY: {
ATTEMPTS: 3,
DELAY: 1000,
}
} as const;
export default API_CONFIG;

View File

@@ -0,0 +1,203 @@
"use client";
import { useState, useEffect } from 'react';
import { aboutService, AboutPageData, AboutBanner, AboutService, AboutProcess, AboutJourney } from '../api/aboutService';
interface UseAboutReturn {
data: AboutPageData | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
interface UseAboutBannerReturn {
data: AboutBanner[] | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
interface UseAboutServiceReturn {
data: AboutService[] | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
interface UseAboutProcessReturn {
data: AboutProcess[] | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
interface UseAboutJourneyReturn {
data: AboutJourney[] | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
/**
* Hook to fetch all about page data
*/
export const useAbout = (): UseAboutReturn => {
const [data, setData] = useState<AboutPageData | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await aboutService.getAboutPageData();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
refetch: fetchData,
};
};
/**
* Hook to fetch about banners
*/
export const useAboutBanners = (): UseAboutBannerReturn => {
const [data, setData] = useState<AboutBanner[] | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await aboutService.getBanners();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
refetch: fetchData,
};
};
/**
* Hook to fetch about services
*/
export const useAboutServices = (): UseAboutServiceReturn => {
const [data, setData] = useState<AboutService[] | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await aboutService.getServices();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
refetch: fetchData,
};
};
/**
* Hook to fetch about processes
*/
export const useAboutProcesses = (): UseAboutProcessReturn => {
const [data, setData] = useState<AboutProcess[] | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await aboutService.getProcesses();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
refetch: fetchData,
};
};
/**
* Hook to fetch about journeys
*/
export const useAboutJourneys = (): UseAboutJourneyReturn => {
const [data, setData] = useState<AboutJourney[] | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await aboutService.getJourneys();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
refetch: fetchData,
};
};

View File

@@ -0,0 +1,250 @@
import { useState, useEffect } from 'react';
import {
blogService,
BlogPost,
BlogPostListResponse,
BlogCategory,
BlogTag,
BlogAuthor
} from '../api/blogService';
// Hook for fetching all blog posts
export const useBlogPosts = (params?: {
category?: string;
tag?: string;
author?: number;
search?: string;
featured?: boolean;
ordering?: string;
page?: number;
page_size?: number;
}) => {
const [posts, setPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const [pagination, setPagination] = useState<{
count: number;
next: string | null;
previous: string | null;
}>({ count: 0, next: null, previous: null });
useEffect(() => {
const fetchPosts = async () => {
try {
setLoading(true);
const data = await blogService.getPosts(params);
setPosts(data.results);
setPagination({
count: data.count,
next: data.next,
previous: data.previous
});
setError(null);
} catch (err) {
setError(err as Error);
setPosts([]);
} finally {
setLoading(false);
}
};
fetchPosts();
}, [params?.category, params?.tag, params?.author, params?.search, params?.page, params?.page_size]);
return { posts, loading, error, pagination, totalCount: pagination.count };
};
// Hook for fetching a single blog post by slug
export const useBlogPost = (slug: string | null) => {
const [post, setPost] = useState<BlogPost | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!slug) {
setLoading(false);
return;
}
const fetchPost = async () => {
try {
setLoading(true);
const data = await blogService.getPostBySlug(slug);
setPost(data);
setError(null);
} catch (err) {
setError(err as Error);
setPost(null);
} finally {
setLoading(false);
}
};
fetchPost();
}, [slug]);
return { post, loading, error };
};
// Hook for fetching featured blog posts
export const useFeaturedPosts = () => {
const [posts, setPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchFeaturedPosts = async () => {
try {
setLoading(true);
const data = await blogService.getFeaturedPosts();
setPosts(data);
setError(null);
} catch (err) {
setError(err as Error);
setPosts([]);
} finally {
setLoading(false);
}
};
fetchFeaturedPosts();
}, []);
return { posts, loading, error };
};
// Hook for fetching latest blog posts
export const useLatestPosts = (limit: number = 5) => {
const [posts, setPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchLatestPosts = async () => {
try {
setLoading(true);
const data = await blogService.getLatestPosts(limit);
setPosts(data);
setError(null);
} catch (err) {
setError(err as Error);
setPosts([]);
} finally {
setLoading(false);
}
};
fetchLatestPosts();
}, [limit]);
return { posts, loading, error };
};
// Hook for fetching popular blog posts
export const usePopularPosts = (limit: number = 5) => {
const [posts, setPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchPopularPosts = async () => {
try {
setLoading(true);
const data = await blogService.getPopularPosts(limit);
setPosts(data);
setError(null);
} catch (err) {
setError(err as Error);
setPosts([]);
} finally {
setLoading(false);
}
};
fetchPopularPosts();
}, [limit]);
return { posts, loading, error };
};
// Hook for fetching blog categories
export const useBlogCategories = () => {
const [categories, setCategories] = useState<BlogCategory[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchCategories = async () => {
try {
setLoading(true);
const data = await blogService.getCategories();
setCategories(data);
setError(null);
} catch (err) {
setError(err as Error);
setCategories([]);
} finally {
setLoading(false);
}
};
fetchCategories();
}, []);
return { categories, loading, error };
};
// Hook for fetching blog tags
export const useBlogTags = () => {
const [tags, setTags] = useState<BlogTag[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchTags = async () => {
try {
setLoading(true);
const data = await blogService.getTags();
setTags(data);
setError(null);
} catch (err) {
setError(err as Error);
setTags([]);
} finally {
setLoading(false);
}
};
fetchTags();
}, []);
return { tags, loading, error };
};
// Hook for fetching blog authors
export const useBlogAuthors = () => {
const [authors, setAuthors] = useState<BlogAuthor[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchAuthors = async () => {
try {
setLoading(true);
const data = await blogService.getAuthors();
setAuthors(data);
setError(null);
} catch (err) {
setError(err as Error);
setAuthors([]);
} finally {
setLoading(false);
}
};
fetchAuthors();
}, []);
return { authors, loading, error };
};

View File

@@ -0,0 +1,99 @@
import { useState, useEffect } from 'react';
import { careerService, JobPosition } from '../api/careerService';
/**
* Custom hook to fetch all job positions
*/
export const useJobs = () => {
const [jobs, setJobs] = useState<JobPosition[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchJobs = async () => {
try {
setLoading(true);
const data = await careerService.getAllJobs();
setJobs(data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
fetchJobs();
}, []);
return { jobs, loading, error };
};
/**
* Custom hook to fetch a single job by slug
*/
export const useJob = (slug: string) => {
const [job, setJob] = useState<JobPosition | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
console.log('🔍 useJob hook called with slug:', slug);
if (!slug) {
console.log('❌ No slug provided, setting loading to false');
setLoading(false);
return;
}
const fetchJob = async () => {
try {
console.log('📡 Fetching job data for slug:', slug);
setLoading(true);
const data = await careerService.getJobBySlug(slug);
console.log('✅ Job data fetched successfully:', data);
setJob(data);
setError(null);
} catch (err) {
console.error('❌ Error fetching job data:', err);
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
console.log('🔄 Setting loading to false');
setLoading(false);
}
};
fetchJob();
}, [slug]);
return { job, loading, error };
};
/**
* Custom hook to fetch featured jobs
*/
export const useFeaturedJobs = () => {
const [jobs, setJobs] = useState<JobPosition[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchFeaturedJobs = async () => {
try {
setLoading(true);
const data = await careerService.getFeaturedJobs();
setJobs(data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
fetchFeaturedJobs();
}, []);
return { jobs, loading, error };
};

View File

@@ -0,0 +1,340 @@
import { useState, useEffect } from 'react';
import caseStudyService, {
CaseStudy,
CaseStudyCategory,
Client,
CaseStudyListResponse,
} from '../api/caseStudyService';
// Hook to fetch all case studies
export const useCaseStudies = (params?: {
category?: string;
client?: string;
search?: string;
featured?: boolean;
ordering?: string;
page?: number;
page_size?: number;
}) => {
const [caseStudies, setCaseStudies] = useState<CaseStudy[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const [pagination, setPagination] = useState<{
count: number;
next: string | null;
previous: string | null;
} | null>(null);
useEffect(() => {
const fetchCaseStudies = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getCaseStudies(params);
setCaseStudies(data.results);
setPagination({
count: data.count,
next: data.next,
previous: data.previous,
});
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchCaseStudies();
}, [
params?.category,
params?.client,
params?.search,
params?.featured,
params?.ordering,
params?.page,
params?.page_size,
]);
return { caseStudies, loading, error, pagination };
};
// Hook to fetch a single case study by slug
export const useCaseStudy = (slug: string | null) => {
const [caseStudy, setCaseStudy] = useState<CaseStudy | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!slug) {
setLoading(false);
return;
}
const fetchCaseStudy = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getCaseStudyBySlug(slug);
setCaseStudy(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchCaseStudy();
}, [slug]);
return { caseStudy, loading, error };
};
// Hook to fetch featured case studies
export const useFeaturedCaseStudies = () => {
const [featuredCaseStudies, setFeaturedCaseStudies] = useState<CaseStudy[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchFeaturedCaseStudies = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getFeaturedCaseStudies();
setFeaturedCaseStudies(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchFeaturedCaseStudies();
}, []);
return { featuredCaseStudies, loading, error };
};
// Hook to fetch latest case studies
export const useLatestCaseStudies = (limit: number = 6) => {
const [latestCaseStudies, setLatestCaseStudies] = useState<CaseStudy[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchLatestCaseStudies = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getLatestCaseStudies(limit);
setLatestCaseStudies(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchLatestCaseStudies();
}, [limit]);
return { latestCaseStudies, loading, error };
};
// Hook to fetch popular case studies
export const usePopularCaseStudies = (limit: number = 6) => {
const [popularCaseStudies, setPopularCaseStudies] = useState<CaseStudy[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchPopularCaseStudies = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getPopularCaseStudies(limit);
setPopularCaseStudies(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchPopularCaseStudies();
}, [limit]);
return { popularCaseStudies, loading, error };
};
// Hook to fetch related case studies
export const useRelatedCaseStudies = (slug: string | null) => {
const [relatedCaseStudies, setRelatedCaseStudies] = useState<CaseStudy[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!slug) {
setLoading(false);
return;
}
const fetchRelatedCaseStudies = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getRelatedCaseStudies(slug);
setRelatedCaseStudies(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchRelatedCaseStudies();
}, [slug]);
return { relatedCaseStudies, loading, error };
};
// Hook to fetch case study categories
export const useCaseStudyCategories = () => {
const [categories, setCategories] = useState<CaseStudyCategory[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchCategories = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getCategories();
setCategories(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchCategories();
}, []);
return { categories, loading, error };
};
// Hook to fetch categories with case studies
export const useCategoriesWithCaseStudies = () => {
const [categories, setCategories] = useState<CaseStudyCategory[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchCategories = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getCategoriesWithCaseStudies();
setCategories(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchCategories();
}, []);
return { categories, loading, error };
};
// Hook to fetch all clients
export const useClients = () => {
const [clients, setClients] = useState<Client[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchClients = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getClients();
setClients(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchClients();
}, []);
return { clients, loading, error };
};
// Hook to fetch a single client by slug
export const useClient = (slug: string | null) => {
const [client, setClient] = useState<Client | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!slug) {
setLoading(false);
return;
}
const fetchClient = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getClientBySlug(slug);
setClient(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchClient();
}, [slug]);
return { client, loading, error };
};
// Hook to fetch case studies for a specific client
export const useClientCaseStudies = (slug: string | null) => {
const [caseStudies, setCaseStudies] = useState<CaseStudy[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!slug) {
setLoading(false);
return;
}
const fetchClientCaseStudies = async () => {
try {
setLoading(true);
setError(null);
const data = await caseStudyService.getClientCaseStudies(slug);
setCaseStudies(data.results);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
fetchClientCaseStudies();
}, [slug]);
return { caseStudies, loading, error };
};

View File

@@ -0,0 +1,128 @@
"use client";
import { useState, useEffect } from 'react';
import { Policy, PolicyListItem, getPolicies, getPolicyByType, getPolicyById } from '../api/policyService';
interface UsePoliciesReturn {
data: PolicyListItem[] | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
interface UsePolicyReturn {
data: Policy | null;
isLoading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}
/**
* Hook to fetch all policies
*/
export const usePolicies = (): UsePoliciesReturn => {
const [data, setData] = useState<PolicyListItem[] | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await getPolicies();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
refetch: fetchData,
};
};
/**
* Hook to fetch a policy by type
*/
export const usePolicy = (type: 'privacy' | 'terms' | 'support' | null): UsePolicyReturn => {
const [data, setData] = useState<Policy | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = async () => {
if (!type) {
setIsLoading(false);
return;
}
try {
setIsLoading(true);
setError(null);
const result = await getPolicyByType(type);
setData(result);
} catch (err) {
setError(err instanceof Error ? err : new Error('An error occurred'));
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchData();
}, [type]);
return {
data,
isLoading,
error,
refetch: fetchData,
};
};
/**
* Hook to fetch a policy by ID
*/
export const usePolicyById = (id: number | null): UsePolicyReturn => {
const [data, setData] = useState<Policy | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = async () => {
if (!id) {
setIsLoading(false);
return;
}
try {
setIsLoading(true);
setError(null);
const result = await getPolicyById(id);
setData(result);
} catch (err) {
setError(err instanceof Error ? err : new Error('An error occurred'));
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchData();
}, [id]);
return {
data,
isLoading,
error,
refetch: fetchData,
};
};

View File

@@ -0,0 +1,302 @@
"use client";
import { useState, useEffect, useCallback } from 'react';
import { serviceService, Service, ServiceListResponse, ServiceStats, ServiceSearchResponse } from '../api/serviceService';
// Hook for fetching all services
export const useServices = (params?: {
featured?: boolean;
category?: string;
min_price?: number;
max_price?: number;
search?: string;
ordering?: string;
page?: number;
}) => {
const [services, setServices] = useState<Service[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [pagination, setPagination] = useState<{
count: number;
next: string | null;
previous: string | null;
} | null>(null);
const fetchServices = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await serviceService.getServices(params);
setServices(response.results);
setPagination({
count: response.count,
next: response.next,
previous: response.previous,
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch services');
} finally {
setLoading(false);
}
}, [params]);
useEffect(() => {
fetchServices();
}, [fetchServices]);
return {
services,
loading,
error,
pagination,
refetch: fetchServices,
};
};
// Hook for fetching a single service by slug
export const useService = (slug: string) => {
const [service, setService] = useState<Service | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchService = useCallback(async () => {
if (!slug) return;
try {
setLoading(true);
setError(null);
const response = await serviceService.getServiceBySlug(slug);
setService(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch service');
} finally {
setLoading(false);
}
}, [slug]);
useEffect(() => {
fetchService();
}, [fetchService]);
return {
service,
loading,
error,
refetch: fetchService,
};
};
// Hook for fetching featured services
export const useFeaturedServices = () => {
const [services, setServices] = useState<Service[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchFeaturedServices = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await serviceService.getFeaturedServices();
setServices(response.results);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch featured services');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchFeaturedServices();
}, [fetchFeaturedServices]);
return {
services,
loading,
error,
refetch: fetchFeaturedServices,
};
};
// Hook for searching services
export const useServiceSearch = (query: string) => {
const [results, setResults] = useState<Service[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [count, setCount] = useState(0);
const searchServices = useCallback(async (searchQuery: string) => {
if (!searchQuery.trim()) {
setResults([]);
setCount(0);
return;
}
try {
setLoading(true);
setError(null);
const response = await serviceService.searchServices(searchQuery);
setResults(response.results);
setCount(response.count);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to search services');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
const timeoutId = setTimeout(() => {
searchServices(query);
}, 300); // Debounce search
return () => clearTimeout(timeoutId);
}, [query, searchServices]);
return {
results,
loading,
error,
count,
search: searchServices,
};
};
// Hook for fetching service statistics
export const useServiceStats = () => {
const [stats, setStats] = useState<ServiceStats | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchStats = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await serviceService.getServiceStats();
setStats(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch service stats');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchStats();
}, [fetchStats]);
return {
stats,
loading,
error,
refetch: fetchStats,
};
};
// Hook for managing service state (for admin operations)
export const useServiceManagement = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const createService = useCallback(async (serviceData: Partial<Service>) => {
try {
setLoading(true);
setError(null);
const response = await serviceService.createService(serviceData);
return response;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create service');
throw err;
} finally {
setLoading(false);
}
}, []);
const updateService = useCallback(async (slug: string, serviceData: Partial<Service>) => {
try {
setLoading(true);
setError(null);
const response = await serviceService.updateService(slug, serviceData);
return response;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update service');
throw err;
} finally {
setLoading(false);
}
}, []);
const deleteService = useCallback(async (slug: string) => {
try {
setLoading(true);
setError(null);
await serviceService.deleteService(slug);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete service');
throw err;
} finally {
setLoading(false);
}
}, []);
const uploadServiceImage = useCallback(async (slug: string, imageFile: File) => {
try {
setLoading(true);
setError(null);
const response = await serviceService.uploadServiceImage(slug, imageFile);
return response;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to upload service image');
throw err;
} finally {
setLoading(false);
}
}, []);
return {
loading,
error,
createService,
updateService,
deleteService,
uploadServiceImage,
};
};
// Hook for navigation services (for header menu)
export const useNavigationServices = () => {
const [services, setServices] = useState<Service[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchNavigationServices = useCallback(async () => {
try {
setLoading(true);
setError(null);
// Fetch only active services, ordered by display_order
const response = await serviceService.getServices({
ordering: 'display_order',
page: 1
});
// Filter only active services (handle null values as active)
const activeServices = response.results.filter(service => service.is_active !== false);
setServices(activeServices);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch navigation services');
// Set empty array on error to prevent navigation issues
setServices([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchNavigationServices();
}, [fetchNavigationServices]);
return {
services,
loading,
error,
refetch: fetchNavigationServices,
};
};

View File

@@ -0,0 +1,251 @@
import { useState, useEffect } from 'react';
import {
getTicketCategories,
getTicketStatuses,
getTicketPriorities,
getKnowledgeBaseCategories,
getKnowledgeBaseArticles,
getFeaturedArticles,
getKnowledgeBaseArticle,
getArticlesByCategory,
TicketCategory,
TicketStatus,
TicketPriority,
KnowledgeBaseCategory,
KnowledgeBaseArticle
} from '../api/supportService';
/**
* Hook to fetch ticket categories
*/
export const useTicketCategories = () => {
const [categories, setCategories] = useState<TicketCategory[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchCategories = async () => {
try {
setLoading(true);
const data = await getTicketCategories();
setCategories(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch ticket categories');
} finally {
setLoading(false);
}
};
fetchCategories();
}, []);
return { categories, loading, error };
};
/**
* Hook to fetch ticket statuses
*/
export const useTicketStatuses = () => {
const [statuses, setStatuses] = useState<TicketStatus[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchStatuses = async () => {
try {
setLoading(true);
const data = await getTicketStatuses();
setStatuses(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch ticket statuses');
} finally {
setLoading(false);
}
};
fetchStatuses();
}, []);
return { statuses, loading, error };
};
/**
* Hook to fetch ticket priorities
*/
export const useTicketPriorities = () => {
const [priorities, setPriorities] = useState<TicketPriority[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchPriorities = async () => {
try {
setLoading(true);
const data = await getTicketPriorities();
setPriorities(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch ticket priorities');
} finally {
setLoading(false);
}
};
fetchPriorities();
}, []);
return { priorities, loading, error };
};
/**
* Hook to fetch knowledge base categories
*/
export const useKnowledgeBaseCategories = () => {
const [categories, setCategories] = useState<KnowledgeBaseCategory[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchCategories = async () => {
try {
setLoading(true);
const data = await getKnowledgeBaseCategories();
setCategories(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch knowledge base categories');
} finally {
setLoading(false);
}
};
fetchCategories();
}, []);
return { categories, loading, error };
};
/**
* Hook to fetch knowledge base articles with optional search
*/
export const useKnowledgeBaseArticles = (search?: string) => {
const [articles, setArticles] = useState<KnowledgeBaseArticle[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchArticles = async () => {
try {
setLoading(true);
const data = await getKnowledgeBaseArticles(search);
setArticles(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch knowledge base articles');
} finally {
setLoading(false);
}
};
fetchArticles();
}, [search]);
return { articles, loading, error };
};
/**
* Hook to fetch featured knowledge base articles
*/
export const useFeaturedArticles = () => {
const [articles, setArticles] = useState<KnowledgeBaseArticle[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchArticles = async () => {
try {
setLoading(true);
const data = await getFeaturedArticles();
setArticles(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch featured articles');
} finally {
setLoading(false);
}
};
fetchArticles();
}, []);
return { articles, loading, error };
};
/**
* Hook to fetch a single knowledge base article
*/
export const useKnowledgeBaseArticle = (slug: string | null) => {
const [article, setArticle] = useState<KnowledgeBaseArticle | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!slug) {
setLoading(false);
return;
}
const fetchArticle = async () => {
try {
setLoading(true);
const data = await getKnowledgeBaseArticle(slug);
setArticle(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch knowledge base article');
} finally {
setLoading(false);
}
};
fetchArticle();
}, [slug]);
return { article, loading, error };
};
/**
* Hook to fetch articles by category
*/
export const useArticlesByCategory = (categorySlug: string | null) => {
const [articles, setArticles] = useState<KnowledgeBaseArticle[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!categorySlug) {
setLoading(false);
return;
}
const fetchArticles = async () => {
try {
setLoading(true);
const data = await getArticlesByCategory(categorySlug);
setArticles(data);
setError(null);
} catch (err: any) {
setError(err.message || 'Failed to fetch articles by category');
} finally {
setLoading(false);
}
};
fetchArticles();
}, [categorySlug]);
return { articles, loading, error };
};

View File

@@ -0,0 +1,55 @@
// Image utility functions
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
export const FALLBACK_IMAGES = {
BLOG: '/images/blog/blog-poster.png',
CASE_STUDY: '/images/case/poster.png',
SERVICE: '/images/service/thumb-one.png',
GALLERY: '/images/masonry/one.png',
BANNER: '/images/banner/banner-bg.png',
DEFAULT: '/images/logo.png'
};
export function getValidImageUrl(imageUrl?: string, fallback?: string): string {
if (!imageUrl || imageUrl.trim() === '') {
return fallback || FALLBACK_IMAGES.DEFAULT;
}
// If it's already a full URL, return as is
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
return imageUrl;
}
// If it starts with /media/, it's a Django media file - prepend API base URL
if (imageUrl.startsWith('/media/')) {
return `${API_BASE_URL}${imageUrl}`;
}
// If it starts with /images/, it's a local public file
if (imageUrl.startsWith('/images/')) {
return imageUrl;
}
// If it starts with /, check if it's a media file
if (imageUrl.startsWith('/')) {
// If it contains /media/, prepend API base URL
if (imageUrl.includes('/media/')) {
return `${API_BASE_URL}${imageUrl}`;
}
return imageUrl;
}
// Otherwise, assume it's a public path and add /
return `/${imageUrl}`;
}
// Alias for backward compatibility
export const getImageUrl = getValidImageUrl;
export function getImageAlt(title?: string, fallback: string = 'Image'): string {
return title ? `${title} - Image` : fallback;
}
// Export getValidImageAlt as an alias for getImageAlt for backward compatibility
export const getValidImageAlt = getImageAlt;

View File

@@ -0,0 +1,297 @@
import { Metadata } from 'next';
// Site Configuration
export const SITE_CONFIG = {
name: 'GNX Soft',
shortName: 'GNX',
description: 'Leading enterprise software development company providing custom solutions, data replication, incident management, AI-powered business intelligence, and comprehensive system integrations for modern businesses.',
url: process.env.NEXT_PUBLIC_SITE_URL || 'https://gnxsoft.com',
ogImage: '/images/og-image.png',
email: 'info@gnxsoft.com',
phone: '+359 896 13 80 30',
address: {
street: 'Tsar Simeon I, 56',
city: 'Burgas',
state: 'BG',
zip: '8000',
country: 'Bulgaria',
},
social: {
linkedin: 'https://www.linkedin.com/company/gnxtech',
github: 'https://github.com/gnxtech',
},
businessHours: 'Monday - Friday: 9:00 AM - 6:00 PM PST',
foundedYear: 2020,
};
// Default SEO Configuration
export const DEFAULT_SEO = {
title: `${SITE_CONFIG.name} | Enterprise Software Development & IT Solutions`,
description: SITE_CONFIG.description,
keywords: [
'Enterprise Software Development',
'Custom Software Development',
'Data Replication Services',
'Incident Management SaaS',
'AI Business Intelligence',
'Backend Engineering',
'Frontend Engineering',
'Systems Integration',
'External Systems Integration',
'Payment Terminal Integration',
'ERP Integration',
'Cloud Platform Integration',
'Fiscal Printer Integration',
'Enterprise Technology Solutions',
'Digital Transformation',
'Software Consulting',
'API Development',
'Microservices Architecture',
'Cloud Migration',
'DevOps Services',
],
};
// Generate metadata for pages
interface PageMetadataProps {
title?: string;
description?: string;
keywords?: string[];
image?: string;
url?: string;
type?: 'website' | 'article' | 'product' | 'service';
publishedTime?: string;
modifiedTime?: string;
author?: string;
noindex?: boolean;
nofollow?: boolean;
}
export function generateMetadata({
title,
description,
keywords = [],
image,
url,
type = 'website',
publishedTime,
modifiedTime,
author,
noindex = false,
nofollow = false,
}: PageMetadataProps = {}): Metadata {
const pageTitle = title
? `${title} | ${SITE_CONFIG.name}`
: DEFAULT_SEO.title;
const pageDescription = description || DEFAULT_SEO.description;
const pageImage = image
? `${SITE_CONFIG.url}${image}`
: `${SITE_CONFIG.url}${SITE_CONFIG.ogImage}`;
const pageUrl = url ? `${SITE_CONFIG.url}${url}` : SITE_CONFIG.url;
const allKeywords = [...DEFAULT_SEO.keywords, ...keywords];
const metadata: Metadata = {
title: pageTitle,
description: pageDescription,
keywords: allKeywords,
authors: [
{
name: author || SITE_CONFIG.name,
url: SITE_CONFIG.url,
},
],
creator: SITE_CONFIG.name,
publisher: SITE_CONFIG.name,
icons: {
icon: '/images/logo-light.png',
shortcut: '/images/logo-light.png',
apple: '/images/logo-light.png',
},
formatDetection: {
email: false,
address: false,
telephone: false,
},
metadataBase: new URL(SITE_CONFIG.url),
alternates: {
canonical: pageUrl,
},
openGraph: {
type: type === 'article' ? 'article' : 'website',
locale: 'en_US',
url: pageUrl,
siteName: SITE_CONFIG.name,
title: pageTitle,
description: pageDescription,
images: [
{
url: pageImage,
width: 1200,
height: 630,
alt: title || SITE_CONFIG.name,
},
],
},
twitter: {
card: 'summary_large_image',
title: pageTitle,
description: pageDescription,
images: [pageImage],
},
robots: {
index: !noindex,
follow: !nofollow,
googleBot: {
index: !noindex,
follow: !nofollow,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
verification: {
google: process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION,
yandex: process.env.NEXT_PUBLIC_YANDEX_VERIFICATION,
},
};
// Add article-specific metadata
if (type === 'article' && (publishedTime || modifiedTime)) {
metadata.openGraph = {
...metadata.openGraph,
type: 'article',
publishedTime,
modifiedTime,
authors: [author || SITE_CONFIG.name],
};
}
return metadata;
}
// Service-specific metadata generator
export function generateServiceMetadata(service: {
title: string;
description: string;
short_description?: string;
slug: string;
category?: { name: string };
technologies?: string;
duration?: string;
}) {
const keywords = [
service.title,
`${service.title} Services`,
service.category?.name || 'Enterprise Services',
'Custom Development',
'Enterprise Solutions',
];
if (service.technologies) {
const techs = service.technologies.split(',').map((t) => t.trim());
keywords.push(...techs);
}
return generateMetadata({
title: service.title,
description: service.short_description || service.description,
keywords,
url: `/services/${service.slug}`,
type: 'service' as any,
});
}
// Blog-specific metadata generator
export function generateBlogMetadata(post: {
title: string;
description?: string;
excerpt?: string;
slug: string;
image?: string;
published_at?: string;
updated_at?: string;
author?: { name: string };
category?: { name: string };
tags?: string[];
}) {
const keywords = [
post.category?.name || 'Technology',
'Blog',
'Tech Insights',
...(post.tags || []),
];
return generateMetadata({
title: post.title,
description: post.description || post.excerpt,
keywords,
image: post.image,
url: `/insights/${post.slug}`,
type: 'article',
publishedTime: post.published_at,
modifiedTime: post.updated_at,
author: post.author?.name,
});
}
// Case Study metadata generator
export function generateCaseStudyMetadata(caseStudy: {
title: string;
description?: string;
excerpt?: string;
slug: string;
image?: string;
client_name?: string;
industry?: string;
technologies?: string;
}) {
const keywords = [
'Case Study',
caseStudy.client_name || '',
caseStudy.industry || '',
'Success Story',
'Client Project',
];
if (caseStudy.technologies) {
const techs = caseStudy.technologies.split(',').map((t) => t.trim());
keywords.push(...techs);
}
return generateMetadata({
title: caseStudy.title,
description: caseStudy.description || caseStudy.excerpt,
keywords: keywords.filter(Boolean),
image: caseStudy.image,
url: `/case-study/${caseStudy.slug}`,
type: 'article',
});
}
// Career metadata generator
export function generateCareerMetadata(job: {
title: string;
description?: string;
slug: string;
location?: string;
department?: string;
employment_type?: string;
}) {
const keywords = [
job.title,
'Career',
'Job Opening',
job.department || '',
job.location || '',
job.employment_type || '',
'Join Our Team',
].filter(Boolean);
return generateMetadata({
title: `${job.title} - Careers`,
description: job.description,
keywords,
url: `/career/${job.slug}`,
});
}