This commit is contained in:
Iliyan Angelov
2025-10-08 13:46:46 +03:00
parent d48c54e2c5
commit 18ae8b9f88
94 changed files with 8882 additions and 1682 deletions

View File

@@ -0,0 +1,437 @@
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) {
console.error('Error fetching blog posts:', 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) {
console.error('Error fetching blog post:', 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) {
console.error('Error fetching featured posts:', 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) {
console.error('Error fetching latest posts:', 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) {
console.error('Error fetching popular posts:', 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) {
console.error('Error fetching related posts:', 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) {
console.error('Error fetching blog categories:', 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) {
console.error('Error fetching categories with posts:', 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) {
console.error('Error fetching blog category:', 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) {
console.error('Error fetching blog tags:', 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) {
console.error('Error fetching posts by tag:', 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) {
console.error('Error fetching blog authors:', 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) {
console.error('Error fetching posts by author:', 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) {
console.error('Error fetching comments:', 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) {
console.error('Error creating comment:', error);
throw error;
}
},
};

View File

@@ -0,0 +1,375 @@
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) {
console.error('Error fetching case studies:', 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) {
console.error(`Error fetching case study ${slug}:`, 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) {
console.error('Error fetching featured case studies:', 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) {
console.error('Error fetching latest case studies:', 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) {
console.error('Error fetching popular case studies:', 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) {
console.error(`Error fetching related case studies for ${slug}:`, 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) {
console.error('Error fetching case study categories:', 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) {
console.error('Error fetching categories with case studies:', 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) {
console.error('Error fetching clients:', 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) {
console.error(`Error fetching client ${slug}:`, 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) {
console.error(`Error fetching case studies for client ${slug}:`, error);
throw error;
}
},
};
export default caseStudyService;

View File

@@ -9,6 +9,9 @@ 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',

View File

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

View File

@@ -0,0 +1,351 @@
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);
console.error('Error fetching case studies:', err);
} 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);
console.error(`Error fetching case study ${slug}:`, err);
} 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);
console.error('Error fetching featured case studies:', err);
} 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);
console.error('Error fetching latest case studies:', err);
} 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);
console.error('Error fetching popular case studies:', err);
} 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);
console.error(`Error fetching related case studies for ${slug}:`, err);
} 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);
console.error('Error fetching case study categories:', err);
} 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);
console.error('Error fetching categories with case studies:', err);
} 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);
console.error('Error fetching clients:', err);
} 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);
console.error(`Error fetching client ${slug}:`, err);
} 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);
console.error(`Error fetching case studies for client ${slug}:`, err);
} finally {
setLoading(false);
}
};
fetchClientCaseStudies();
}, [slug]);
return { caseStudies, loading, error };
};

View File

@@ -1,5 +1,7 @@
// 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',
@@ -19,8 +21,22 @@ export function getValidImageUrl(imageUrl?: string, fallback?: string): string {
return imageUrl;
}
// If it starts with /, it's already a public path
// 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;
}
@@ -28,6 +44,9 @@ export function getValidImageUrl(imageUrl?: string, fallback?: string): string {
return `/${imageUrl}`;
}
// Alias for backward compatibility
export const getImageUrl = getValidImageUrl;
export function getImageAlt(title?: string, fallback: string = 'Image'): string {
return title ? `${title} - Image` : fallback;
}