forked from UKSOURCE/ipv6
init
This commit is contained in:
+323
@@ -0,0 +1,323 @@
|
||||
import {
|
||||
BlogListResponse,
|
||||
BlogDetailResponse,
|
||||
BlogFeaturedResponse,
|
||||
BlogRecentResponse,
|
||||
CategoryListResponse,
|
||||
CategoryDetailResponse,
|
||||
TagListResponse,
|
||||
TagDetailResponse,
|
||||
BlogQueryParams,
|
||||
} from '../types/blog';
|
||||
|
||||
/**
|
||||
* Lấy API URL từ environment variable
|
||||
*/
|
||||
const getApiUrl = (): string | null => {
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
||||
// Nếu chưa cấu hình API URL, trả về null để skip việc gọi API (tránh lỗi fetch failed)
|
||||
if (!apiUrl) {
|
||||
return null;
|
||||
}
|
||||
return apiUrl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blog list từ API, trả về null nếu thất bại (để caller fallback sang local JSON)
|
||||
*/
|
||||
export const fetchBlogList = async (
|
||||
params?: BlogQueryParams
|
||||
): Promise<BlogListResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (params?.page) queryParams.append('page', params.page.toString());
|
||||
if (params?.limit) queryParams.append('limit', params.limit.toString());
|
||||
if (params?.category) queryParams.append('category', params.category);
|
||||
if (params?.tag) queryParams.append('tag', params.tag);
|
||||
if (params?.search) queryParams.append('search', params.search);
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
const url = `${apiUrl}/api/blog${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blog detail by slug từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchBlogDetail = async (
|
||||
slug: string
|
||||
): Promise<BlogDetailResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/${slug}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogDetailResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch featured blogs từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchFeaturedBlogs = async (
|
||||
limit: number = 5
|
||||
): Promise<BlogFeaturedResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/featured?limit=${limit}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogFeaturedResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch recent blogs từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchRecentBlogs = async (
|
||||
limit: number = 5
|
||||
): Promise<BlogRecentResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/recent?limit=${limit}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: BlogRecentResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch categories list từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchCategories = async (): Promise<CategoryListResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/categories`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: CategoryListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch category detail by slug từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchCategoryDetail = async (
|
||||
slug: string
|
||||
): Promise<CategoryDetailResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/categories/${slug}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: CategoryDetailResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch tags list từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchTags = async (): Promise<TagListResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/tags`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: TagListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch popular tags từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchPopularTags = async (
|
||||
limit: number = 10
|
||||
): Promise<TagListResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/tags/popular?limit=${limit}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: TagListResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch tag detail by slug từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchTagDetail = async (
|
||||
slug: string
|
||||
): Promise<TagDetailResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/blog/tags/${slug}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: TagDetailResponse = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blogs by category từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchBlogsByCategory = async (
|
||||
categorySlug: string,
|
||||
params?: Omit<BlogQueryParams, 'category'>
|
||||
): Promise<BlogListResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const categoryResponse = await fetchCategoryDetail(categorySlug);
|
||||
if (!categoryResponse) return null;
|
||||
|
||||
const categoryName = categoryResponse.data.name;
|
||||
return fetchBlogList({ ...params, category: categoryName });
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch blogs by tag từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchBlogsByTag = async (
|
||||
tagSlug: string,
|
||||
params?: Omit<BlogQueryParams, 'tag'>
|
||||
): Promise<BlogListResponse | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const tagResponse = await fetchTagDetail(tagSlug);
|
||||
if (!tagResponse) return null;
|
||||
|
||||
const tagName = tagResponse.data.name;
|
||||
return fetchBlogList({ ...params, tag: tagName });
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import axiosInstance from "../lib/axios";
|
||||
|
||||
export const fetchContentPageData = async <T>(
|
||||
endpoint: string,
|
||||
): Promise<T | null> => {
|
||||
try {
|
||||
const response = await axiosInstance.get<T>(endpoint);
|
||||
return response.data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { HeaderData, HeaderMenuItem } from '@/types/header';
|
||||
|
||||
const CMS_API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
export async function fetchHeaderData(): Promise<HeaderData> {
|
||||
const res = await fetch(`${CMS_API_URL}/api/header`, {
|
||||
next: { revalidate: 60 },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch header: ${res.status}`);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.success) {
|
||||
throw new Error(json.message || 'Header API error');
|
||||
}
|
||||
|
||||
return json.data as HeaderData;
|
||||
}
|
||||
|
||||
export async function fetchHeaderMenu(): Promise<HeaderMenuItem[]> {
|
||||
const res = await fetch(`${CMS_API_URL}/api/header-menu`, {
|
||||
next: { revalidate: 60 },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch menu: ${res.status}`);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.success) {
|
||||
throw new Error(json.message || 'Menu API error');
|
||||
}
|
||||
|
||||
return json.data as HeaderMenuItem[];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Lấy API URL từ environment variable
|
||||
*/
|
||||
const getApiUrl = (): string | null => {
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
if (!apiUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return apiUrl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch home page data từ API
|
||||
* @returns Promise<any>
|
||||
* @throws Error nếu fetch thất bại
|
||||
*/
|
||||
export const fetchHomeData = async (): Promise<any> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/home`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null; // Trả về null để fallback sang dữ liệu local
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import axiosInstance from "@/lib/axios";
|
||||
|
||||
export interface NewsletterSubscribePayload {
|
||||
email: string;
|
||||
pageUrl?: string;
|
||||
}
|
||||
|
||||
export async function subscribeNewsletter(
|
||||
payload: NewsletterSubscribePayload,
|
||||
): Promise<void> {
|
||||
await axiosInstance.post("/api/newsletter/subscribe", payload);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { FiltersData, Programme } from '../types/programme';
|
||||
|
||||
/**
|
||||
* Lấy API URL từ environment variable
|
||||
*/
|
||||
const getApiUrl = (): string | null => {
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
||||
if (!apiUrl) return null;
|
||||
return apiUrl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch tất cả programmes từ API, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchProgrammes = async (): Promise<Programme[] | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/programmes`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: Programme[] = await response.json();
|
||||
return data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch một programme theo id/slug, trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchProgrammeById = async (id: string): Promise<Programme | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/programmes/${id}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: Programme = await response.json();
|
||||
return data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch những programmes được đánh dấu featured (selected: true)
|
||||
* trả về null nếu thất bại
|
||||
*/
|
||||
export const fetchFeaturedProgrammes = async (): Promise<Programme[] | null> => {
|
||||
const data = await fetchProgrammes();
|
||||
if (!data) return null;
|
||||
return data.filter((p) => p.selected === true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch programme filter options from CMS.
|
||||
*/
|
||||
export const fetchProgrammeFilters = async (): Promise<FiltersData | null> => {
|
||||
const apiUrl = getApiUrl();
|
||||
if (!apiUrl) return null;
|
||||
|
||||
const url = `${apiUrl}/api/programmes/filters`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data: FiltersData = await response.json();
|
||||
return data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import axiosInstance from "@/lib/axios";
|
||||
|
||||
export type SubmissionSource = "home" | "request" | "contact" | "partnership";
|
||||
|
||||
export interface SubmitFormPayload {
|
||||
source: SubmissionSource;
|
||||
pageUrl: string;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function submitForm(payload: SubmitFormPayload): Promise<void> {
|
||||
await axiosInstance.post("/api/submissions", payload);
|
||||
}
|
||||
Reference in New Issue
Block a user