Files
homaroma/apps/website/src/lib/cms.ts
T
2026-10-08 08:08:37 +07:00

160 lines
5.8 KiB
TypeScript

import type { Article, Category, CategorySection, Order, Page, Product, ProductDetailSection, StoreSettings } from '@homaroma/types'
import { API_BASE } from './api'
const isDev = process.env.NODE_ENV === 'development'
const defaultRevalidate = 0
type ProductListResponse = {
data?: Product[]
}
export type Coupon = {
id: number
code: string
type: 'PERCENTAGE' | 'FIXED_AMOUNT' | 'FREE_SHIPPING' | string
value: number
minPurchase: number
usageLimit: number | null
usedCount: number
expiresAt?: string | null
}
export type ProductReview = {
id: number
rating: number
comment?: string | null
customerName?: string | null
createdAt: string
customer?: {
displayName?: string | null
pictureUrl?: string | null
} | null
}
export type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: string; status?: number }
export async function fetchJson<T>(path: string, init?: RequestInit & { next?: { revalidate?: number } }): Promise<T | null> {
try {
const res = await fetch(`${API_BASE}${path}`, {
cache: 'no-store',
...init,
next: init?.next ?? { revalidate: 0 },
})
if (!res.ok) {
console.error(`[website-api] ${path} returned ${res.status}`)
return null
}
return await res.json() as T
} catch (error) {
console.error(`[website-api] Failed to fetch ${path}`, error)
return null
}
}
export async function fetchJsonResult<T>(path: string, init?: RequestInit & { next?: { revalidate?: number } }): Promise<ApiResult<T>> {
try {
const res = await fetch(`${API_BASE}${path}`, {
cache: 'no-store',
...init,
next: init?.next ?? { revalidate: 0 },
})
if (!res.ok) {
return { ok: false, error: `${path} returned ${res.status}`, status: res.status }
}
return { ok: true, data: await res.json() as T }
} catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : `Failed to fetch ${path}`,
}
}
}
export async function getCategories(lang = 'all'): Promise<Category[]> {
return await fetchJson<Category[]>(`/categories?lang=${lang}`, { next: { revalidate: 0 } }) ?? []
}
export async function getCategoriesResult(lang = 'all'): Promise<ApiResult<Category[]>> {
return await fetchJsonResult<Category[]>(`/categories?lang=${lang}`, { next: { revalidate: 0 } })
}
export async function getStoreSettings(lang = 'all'): Promise<StoreSettings | null> {
return await fetchJson<StoreSettings>(`/settings?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getStoreSettingsResult(lang = 'all'): Promise<ApiResult<StoreSettings>> {
return await fetchJsonResult<StoreSettings>(`/settings?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getCmsPage(slug: string, lang = 'all'): Promise<Page | null> {
return await fetchJson<Page>(`/pages/${slug}?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getCmsPageResult(slug: string, lang = 'all'): Promise<ApiResult<Page>> {
return await fetchJsonResult<Page>(`/pages/${slug}?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getArticlesResult(): Promise<ApiResult<Article[]>> {
return await fetchJsonResult<Article[]>('/articles?publishedOnly=true', { next: { revalidate: defaultRevalidate } })
}
export async function getActiveCouponsResult(): Promise<ApiResult<Coupon[]>> {
const response = await fetchJsonResult<Coupon[]>('/coupons/active', { next: { revalidate: defaultRevalidate } })
if (!response.ok) return response
return {
ok: true,
data: response.data.filter((coupon) => coupon.usageLimit === null || coupon.usedCount < coupon.usageLimit),
}
}
export async function getOrderResult(id: string | number, lang = 'all'): Promise<ApiResult<Order>> {
const query = lang && lang !== 'all' ? `?lang=${lang}` : ''
return await fetchJsonResult<Order>(`/orders/${id}${query}`, { next: { revalidate: defaultRevalidate } })
}
export async function getProduct(id: string | number, lang = 'all'): Promise<Product | null> {
return await fetchJson<Product>(`/products/${id}?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getProductResult(id: string | number, lang = 'all'): Promise<ApiResult<Product>> {
return await fetchJsonResult<Product>(`/products/${id}?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getProductDetailSectionsResult(id: string | number, lang = 'all'): Promise<ApiResult<ProductDetailSection[]>> {
return await fetchJsonResult<ProductDetailSection[]>(`/products/${id}/detail-sections?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getCategorySectionsResult(id: string | number, lang = 'all'): Promise<ApiResult<CategorySection[]>> {
return await fetchJsonResult<CategorySection[]>(`/categories/${id}/sections?lang=${lang}`, { next: { revalidate: defaultRevalidate } })
}
export async function getProductReviewsResult(id: string | number): Promise<ApiResult<ProductReview[]>> {
return await fetchJsonResult<ProductReview[]>(`/reviews/product/${id}`, { next: { revalidate: defaultRevalidate } })
}
export async function getProducts(query = '', lang = 'all'): Promise<Product[]> {
const separator = query ? '&' : ''
const response = await fetchJson<ProductListResponse>(`/products?${query}${separator}lang=${lang}`, {
next: { revalidate: defaultRevalidate },
})
return response?.data ?? []
}
export async function getProductsResult(query = '', lang = 'all'): Promise<ApiResult<Product[]>> {
const separator = query ? '&' : ''
const response = await fetchJsonResult<ProductListResponse>(`/products?${query}${separator}lang=${lang}`, {
next: { revalidate: defaultRevalidate },
})
if (!response.ok) return response
return { ok: true, data: response.data.data ?? [] }
}