Product pages emit crawler-readable meta, public URLs use id plus Farsi slugs, and the homepage catalog matches the dashboard slot keys. Co-authored-by: Cursor <cursoragent@cursor.com>
41 lines
1.5 KiB
TypeScript
41 lines
1.5 KiB
TypeScript
import type { ApiBlog, ApiCategory, ApiProduct } from "./api";
|
|
import { detailPath, slugifyForUrl } from "./seo-slug";
|
|
|
|
export function productTitle(product: Pick<ApiProduct, "id" | "nameFa" | "title">) {
|
|
return product.nameFa?.trim() || product.title?.trim() || String(product.id);
|
|
}
|
|
|
|
export function categoryTitle(category: Pick<ApiCategory, "id" | "nameFa" | "name">) {
|
|
return category.nameFa?.trim() || category.name?.trim() || String(category.id);
|
|
}
|
|
|
|
/** Public product URL: /products/{id}/{nameFaSlug} — never use legacy DB slug. */
|
|
export function productHref(product: Pick<ApiProduct, "id" | "nameFa" | "title"> | string, title?: string | null) {
|
|
if (typeof product === "string") {
|
|
return detailPath("/products", product, title?.trim() || product);
|
|
}
|
|
return detailPath("/products", product.id, productTitle(product));
|
|
}
|
|
|
|
/** Public category URL: /products/category/{id}/{nameFaSlug} */
|
|
export function categoryHref(category: Pick<ApiCategory, "id" | "nameFa" | "name">) {
|
|
return detailPath("/products/category", category.id, categoryTitle(category));
|
|
}
|
|
|
|
/** Public blog URL: /blog/{id}/{titleSlug} */
|
|
export function blogHref(blog: Pick<ApiBlog, "id" | "title">) {
|
|
return detailPath("/blog", blog.id, blog.title);
|
|
}
|
|
|
|
export function productNameSlug(title: string) {
|
|
return slugifyForUrl(title);
|
|
}
|
|
|
|
export function categoryNameSlug(title: string) {
|
|
return slugifyForUrl(title);
|
|
}
|
|
|
|
export function blogTitleSlug(title: string) {
|
|
return slugifyForUrl(title);
|
|
}
|