105 lines
2.9 KiB
TypeScript
105 lines
2.9 KiB
TypeScript
|
|
import { ArticleDataType } from "@/types/article";
|
||
|
|
import { ProductDataType } from "@/types/products";
|
||
|
|
|
||
|
|
|
||
|
|
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:5000";
|
||
|
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||
|
|
type QueryValue = string | number | boolean | null | undefined;
|
||
|
|
type QueryParams = Record<string, QueryValue | QueryValue[]>;
|
||
|
|
|
||
|
|
interface RequestOptions<TBody = unknown> {
|
||
|
|
method?: HttpMethod;
|
||
|
|
params?: QueryParams;
|
||
|
|
body?: TBody;
|
||
|
|
signal?: AbortSignal;
|
||
|
|
cache?: RequestCache;
|
||
|
|
next?: NextFetchRequestConfig;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ListParams extends QueryParams {
|
||
|
|
limit?: number; // THÊM limit
|
||
|
|
page?: number;
|
||
|
|
offset?: number;
|
||
|
|
q?: string; // tuỳ chọn (search)
|
||
|
|
sort?: string;
|
||
|
|
categoryId?: number | string;
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildURL(path: string, params?: QueryParams) {
|
||
|
|
const url = new URL(path, BASE_URL);
|
||
|
|
if (params) {
|
||
|
|
for (const [k, v] of Object.entries(params)) {
|
||
|
|
if (Array.isArray(v)) {
|
||
|
|
v.forEach((vv) => {
|
||
|
|
if (vv !== undefined && vv !== null) url.searchParams.append(k, String(vv));
|
||
|
|
});
|
||
|
|
} else if (v !== undefined && v !== null) {
|
||
|
|
url.searchParams.set(k, String(v));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return url.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
async function apiRequest<TResp = unknown, TBody = unknown>(
|
||
|
|
endpoint: string,
|
||
|
|
options: RequestOptions<TBody> = {}
|
||
|
|
): Promise<TResp> {
|
||
|
|
const {
|
||
|
|
method = "GET",
|
||
|
|
params,
|
||
|
|
body,
|
||
|
|
signal,
|
||
|
|
cache = "no-store",
|
||
|
|
next,
|
||
|
|
} = options;
|
||
|
|
|
||
|
|
const url = buildURL(endpoint, params);
|
||
|
|
|
||
|
|
const res = await fetch(url, {
|
||
|
|
method,
|
||
|
|
headers: { "Content-Type": "application/json" },
|
||
|
|
body: body && method !== "GET" ? JSON.stringify(body) : undefined,
|
||
|
|
signal,
|
||
|
|
cache,
|
||
|
|
next,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!res.ok) {
|
||
|
|
const msg = await res.text().catch(() => res.statusText);
|
||
|
|
throw new Error(`API ${method} ${endpoint} failed: ${res.status} ${msg}`);
|
||
|
|
}
|
||
|
|
return res.json() as Promise<TResp>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// API cho bài viết
|
||
|
|
export const fetchListArticles = (params?: ListParams) =>
|
||
|
|
apiRequest<{ list: ArticleDataType[]; total?: number }>("/article", {
|
||
|
|
params,
|
||
|
|
});
|
||
|
|
|
||
|
|
// GET /articleDetails?path=:slug
|
||
|
|
export const fetchArticleDetail = (slug: string) =>
|
||
|
|
apiRequest<ArticleDataType>("/articleDetails", {
|
||
|
|
params: { path: slug },
|
||
|
|
});
|
||
|
|
|
||
|
|
// API cho công việc
|
||
|
|
export const fetchListProduct = (params?: ListParams) =>
|
||
|
|
apiRequest<{ list: ProductDataType[]; total?: number }>("/product", {
|
||
|
|
params,
|
||
|
|
});
|
||
|
|
export const fetchProductDetail = (slug: string) =>
|
||
|
|
apiRequest<ProductDataType>("/productDetails", {
|
||
|
|
params: { path: slug },
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
export const fetchProductsByCategoryId = (
|
||
|
|
categoryId: number | string,
|
||
|
|
params?: Omit<ListParams, "categoryId">
|
||
|
|
) =>
|
||
|
|
fetchListProduct({
|
||
|
|
...params,
|
||
|
|
categoryId,
|
||
|
|
});
|