update
This commit is contained in:
325
frontEnd/lib/api/aboutService.ts
Normal file
325
frontEnd/lib/api/aboutService.ts
Normal 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;
|
||||
422
frontEnd/lib/api/blogService.ts
Normal file
422
frontEnd/lib/api/blogService.ts
Normal 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;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
229
frontEnd/lib/api/careerService.ts
Normal file
229
frontEnd/lib/api/careerService.ts
Normal 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();
|
||||
|
||||
364
frontEnd/lib/api/caseStudyService.ts
Normal file
364
frontEnd/lib/api/caseStudyService.ts
Normal 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;
|
||||
|
||||
131
frontEnd/lib/api/contactService.ts
Normal file
131
frontEnd/lib/api/contactService.ts
Normal 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;
|
||||
115
frontEnd/lib/api/policyService.ts
Normal file
115
frontEnd/lib/api/policyService.ts
Normal 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;
|
||||
|
||||
|
||||
495
frontEnd/lib/api/serviceService.ts
Normal file
495
frontEnd/lib/api/serviceService.ts
Normal 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;
|
||||
});
|
||||
},
|
||||
};
|
||||
414
frontEnd/lib/api/supportService.ts
Normal file
414
frontEnd/lib/api/supportService.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
|
||||
46
frontEnd/lib/config/api.ts
Normal file
46
frontEnd/lib/config/api.ts
Normal 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;
|
||||
203
frontEnd/lib/hooks/useAbout.ts
Normal file
203
frontEnd/lib/hooks/useAbout.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
250
frontEnd/lib/hooks/useBlog.ts
Normal file
250
frontEnd/lib/hooks/useBlog.ts
Normal 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 };
|
||||
};
|
||||
|
||||
99
frontEnd/lib/hooks/useCareer.ts
Normal file
99
frontEnd/lib/hooks/useCareer.ts
Normal 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 };
|
||||
};
|
||||
|
||||
340
frontEnd/lib/hooks/useCaseStudy.ts
Normal file
340
frontEnd/lib/hooks/useCaseStudy.ts
Normal 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 };
|
||||
};
|
||||
|
||||
128
frontEnd/lib/hooks/usePolicy.ts
Normal file
128
frontEnd/lib/hooks/usePolicy.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
302
frontEnd/lib/hooks/useServices.ts
Normal file
302
frontEnd/lib/hooks/useServices.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
251
frontEnd/lib/hooks/useSupport.ts
Normal file
251
frontEnd/lib/hooks/useSupport.ts
Normal 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 };
|
||||
};
|
||||
|
||||
55
frontEnd/lib/imageUtils.ts
Normal file
55
frontEnd/lib/imageUtils.ts
Normal 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;
|
||||
297
frontEnd/lib/seo/metadata.ts
Normal file
297
frontEnd/lib/seo/metadata.ts
Normal 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}`,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user