Route user products as /user-products/{id}/{slug}.
Load details via by-id, build links from pathSlug/title, redirect legacy slug URLs, and update the sitemap template. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b36c07e28f
commit
5a6ef9e0e0
@@ -44,6 +44,8 @@ export type UserProductCondition =
|
||||
export type UserProductListItem = {
|
||||
id: string;
|
||||
slug: string;
|
||||
/** SEO path segment for `/user-products/{id}/{pathSlug}`. */
|
||||
pathSlug?: string | null;
|
||||
titleFa: string | null;
|
||||
titleEn: string | null;
|
||||
price: number | string | null;
|
||||
|
||||
@@ -61,6 +61,40 @@ const emptyPage = <T,>(page = 1, pageSize = 12): Paginated<T> => ({
|
||||
pageSize,
|
||||
});
|
||||
|
||||
/** Match Meshkee `slugifyForUrl` — keep Persian/Latin letters + digits. */
|
||||
export function slugifyForUrl(value: string, fallback = "user-product") {
|
||||
const slug = value
|
||||
.trim()
|
||||
.normalize("NFC")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(
|
||||
/[^\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFFa-zA-Z0-9-]+/g,
|
||||
"",
|
||||
)
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
|
||||
return slug || fallback;
|
||||
}
|
||||
|
||||
export function userProductPathSlug(
|
||||
product: Pick<UserProductListItem, "pathSlug" | "titleFa" | "titleEn">,
|
||||
) {
|
||||
const fromApi = product.pathSlug?.trim();
|
||||
if (fromApi) return fromApi;
|
||||
return slugifyForUrl(
|
||||
product.titleFa?.trim() || product.titleEn?.trim() || "",
|
||||
"user-product",
|
||||
);
|
||||
}
|
||||
|
||||
/** Canonical storefront path: `/user-products/{id}/{pathSlug}`. */
|
||||
export function userProductHref(
|
||||
product: Pick<UserProductListItem, "id" | "pathSlug" | "titleFa" | "titleEn">,
|
||||
) {
|
||||
return `/user-products/${encodeURIComponent(product.id)}/${encodeURIComponent(userProductPathSlug(product))}`;
|
||||
}
|
||||
|
||||
export async function listUserProducts(params: ListUserProductsParams = {}) {
|
||||
const page = params.page ?? 1;
|
||||
const pageSize = params.pageSize ?? 12;
|
||||
@@ -81,6 +115,24 @@ export async function listUserProducts(params: ListUserProductsParams = {}) {
|
||||
return emptyPage<UserProductListItem>(page, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserProductById(id: string) {
|
||||
const data = await apiGet<{ product: UserProduct }>(
|
||||
`/user-products/by-id/${encodeURIComponent(id)}`,
|
||||
);
|
||||
return data.product;
|
||||
}
|
||||
|
||||
export async function getUserProductByIdOrNull(id: string) {
|
||||
try {
|
||||
return await getUserProductById(id);
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.status === 404) return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Prefer `getUserProductById` — kept for legacy slug redirects. */
|
||||
export async function getUserProductBySlug(slug: string) {
|
||||
const data = await apiGet<{ product: UserProduct }>(
|
||||
`/user-products/${encodeURIComponent(slug)}`,
|
||||
@@ -97,7 +149,21 @@ export async function getUserProductBySlugOrNull(slug: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Labels + values for specs table. Join form.fields[].id ↔ values[].fieldId. */
|
||||
export async function getUserProductTechnicalInfoById(id: string) {
|
||||
try {
|
||||
return await apiGet<UserProductTechnicalInfo>(
|
||||
`/user-products/by-id/${encodeURIComponent(id)}/technical-info`,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.status === 404) {
|
||||
return { form: null, values: [] } satisfies UserProductTechnicalInfo;
|
||||
}
|
||||
console.error("getUserProductTechnicalInfoById failed", error);
|
||||
return { form: null, values: [] } satisfies UserProductTechnicalInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Prefer `getUserProductTechnicalInfoById`. */
|
||||
export async function getUserProductTechnicalInfo(slug: string) {
|
||||
try {
|
||||
return await apiGet<UserProductTechnicalInfo>(
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
],
|
||||
"templates": {
|
||||
"userProduct": "/user-products/{slug}",
|
||||
"userProduct": "/user-products/{id}/{slug}",
|
||||
"blog": "/blog/{slug}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user