feat: blog section
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
const WP_API_BASE = "https://wpa.feep.ir/wp-json/wp/v2";
|
||||
const FETCH_TIMEOUT_MS = 15_000;
|
||||
|
||||
export interface WpMediaSize {
|
||||
source_url: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export interface WpFeaturedMedia {
|
||||
source_url: string;
|
||||
alt_text?: string;
|
||||
media_details?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
sizes?: Record<string, WpMediaSize>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WpAuthor {
|
||||
id: number;
|
||||
name: string;
|
||||
slug?: string;
|
||||
}
|
||||
|
||||
export interface WpPost {
|
||||
id: number;
|
||||
date: string;
|
||||
modified?: string;
|
||||
slug: string;
|
||||
title: { rendered: string };
|
||||
content: { rendered: string };
|
||||
excerpt: { rendered: string };
|
||||
featured_media: number;
|
||||
categories: number[];
|
||||
author?: number;
|
||||
_embedded?: {
|
||||
"wp:featuredmedia"?: WpFeaturedMedia[];
|
||||
author?: WpAuthor[];
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns thumbnail URL for list/card (medium) or full for single post. */
|
||||
export function getFeaturedImageUrl(
|
||||
post: WpPost,
|
||||
size: "medium" | "medium_large" | "full" = "medium"
|
||||
): string | null {
|
||||
const media = post._embedded?.["wp:featuredmedia"]?.[0];
|
||||
if (!media?.source_url) return null;
|
||||
const sizes = media.media_details?.sizes;
|
||||
if (sizes?.[size]?.source_url) return sizes[size].source_url;
|
||||
if (size !== "full" && sizes?.medium?.source_url) return sizes.medium.source_url;
|
||||
return media.source_url;
|
||||
}
|
||||
|
||||
export interface WpCategory {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface GetPostsResult {
|
||||
posts: WpPost[];
|
||||
total: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface GetPostsOptions {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
search?: string;
|
||||
categoryId?: number;
|
||||
}
|
||||
|
||||
async function fetchWithTimeout(
|
||||
url: string,
|
||||
options?: RequestInit
|
||||
): Promise<Response> {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
...options,
|
||||
signal: controller.signal,
|
||||
next: { revalidate: 0 },
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
return res;
|
||||
} catch (e) {
|
||||
clearTimeout(timeout);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPosts(
|
||||
options: GetPostsOptions = {}
|
||||
): Promise<GetPostsResult> {
|
||||
const { page = 1, perPage = 9, search, categoryId } = options;
|
||||
const params = new URLSearchParams();
|
||||
params.set("page", String(page));
|
||||
params.set("per_page", String(perPage));
|
||||
if (search?.trim()) params.set("search", search.trim());
|
||||
if (categoryId) params.set("categories", String(categoryId));
|
||||
params.set("_embed", "1");
|
||||
|
||||
const url = `${WP_API_BASE}/posts?${params.toString()}`;
|
||||
const res = await fetchWithTimeout(url);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
const total = parseInt(res.headers.get("x-wp-total") ?? "0", 10);
|
||||
const totalPages = parseInt(res.headers.get("x-wp-totalpages") ?? "1", 10);
|
||||
const posts: WpPost[] = await res.json();
|
||||
|
||||
return { posts, total, totalPages };
|
||||
}
|
||||
|
||||
export async function getPostBySlug(slug: string): Promise<WpPost | null> {
|
||||
const decodedSlug = decodeURIComponent(slug);
|
||||
const params = new URLSearchParams({ slug: decodedSlug, _embed: "1" });
|
||||
const url = `${WP_API_BASE}/posts?${params.toString()}`;
|
||||
const res = await fetchWithTimeout(url);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
const posts: WpPost[] = await res.json();
|
||||
return posts.length > 0 ? posts[0] : null;
|
||||
}
|
||||
|
||||
export async function getCategories(): Promise<WpCategory[]> {
|
||||
const url = `${WP_API_BASE}/categories?per_page=100`;
|
||||
const res = await fetchWithTimeout(url);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
const data: WpCategory[] = await res.json();
|
||||
return data.filter((c) => c.count > 0);
|
||||
}
|
||||
Reference in New Issue
Block a user