From d249d958ebb6dcbd34ca83a7f2391a2a95a53028 Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Thu, 27 Aug 2026 07:11:52 +0330 Subject: [PATCH] Add Meshkee sitemap config, technical-info labels, and footer credit. Wire specs to /technical-info for field labels, match Safeteb footer credit, and publish build-time /meshkee/sitemap-config.json for SEO sync. Co-authored-by: Cursor --- package.json | 4 +- public/images/footer/MeshkeeLogo-White.png | Bin 0 -> 33035 bytes scripts/generate-sitemap-config.mjs | 111 +++++++++++++ src/app/blog/[slug]/page.tsx | 9 +- src/app/globals.css | 161 +++++++++++++++++++ src/app/meshkee/sitemap-config.json/route.ts | 17 ++ src/app/user-products/[slug]/page.tsx | 33 ++-- src/components/Footer.tsx | 41 ++++- src/components/PageBanner.tsx | 2 +- src/components/ProductTabs.tsx | 51 ++++-- src/components/TechnicalValuesTable.tsx | 31 ++-- src/lib/api/product-fields.ts | 47 ------ src/lib/api/user-products.ts | 62 +++++++ src/lib/product-technical-values.ts | 90 +++++++++-- src/lib/sitemap-config.generated.json | 39 +++++ 15 files changed, 590 insertions(+), 108 deletions(-) create mode 100755 public/images/footer/MeshkeeLogo-White.png create mode 100644 scripts/generate-sitemap-config.mjs create mode 100644 src/app/meshkee/sitemap-config.json/route.ts delete mode 100644 src/lib/api/product-fields.ts create mode 100644 src/lib/sitemap-config.generated.json diff --git a/package.json b/package.json index 56de16b..ebc7b68 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "version": "0.1.0", "private": true, "scripts": { + "prebuild": "node scripts/generate-sitemap-config.mjs", "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "generate:sitemap-config": "node scripts/generate-sitemap-config.mjs" }, "dependencies": { "next": "16.3.0", diff --git a/public/images/footer/MeshkeeLogo-White.png b/public/images/footer/MeshkeeLogo-White.png new file mode 100755 index 0000000000000000000000000000000000000000..a10ecd330d378ce20d66f4fb430c6f2372d841db GIT binary patch literal 33035 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 literal 0 HcmV?d00001 diff --git a/scripts/generate-sitemap-config.mjs b/scripts/generate-sitemap-config.mjs new file mode 100644 index 0000000..4536f1a --- /dev/null +++ b/scripts/generate-sitemap-config.mjs @@ -0,0 +1,111 @@ +/** + * Scans app page routes and writes src/lib/sitemap-config.generated.json + * for GET /meshkee/sitemap-config.json (Meshkee sitemap sync). + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const appDir = path.join(root, "src", "app"); +const outFile = path.join(root, "src", "lib", "sitemap-config.generated.json"); + +const EXCLUDE_SEGMENTS = new Set([ + "api", + "meshkee", + "admin", + "auth", + "login", + "checkout", + "cart", + "account", +]); + +const PRIORITY = { + "/": { changefreq: "daily", priority: 1.0 }, + "/user-products": { changefreq: "daily", priority: 0.9 }, + "/categories": { changefreq: "weekly", priority: 0.8 }, + "/blog": { changefreq: "weekly", priority: 0.8 }, + "/about": { changefreq: "monthly", priority: 0.6 }, + "/contact": { changefreq: "monthly", priority: 0.5 }, +}; + +function websiteDomain() { + return ( + process.env.NEXT_PUBLIC_SITE_DOMAIN || + process.env.NEXT_PUBLIC_WEBSITE_DOMAIN || + process.env.WEBSITE_DOMAIN || + "www.mashinify.com" + ); +} + +function walkPages(dir, segments = []) { + /** @type {string[]} */ + const pages = []; + if (!fs.existsSync(dir)) return pages; + + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.name.startsWith(".") || entry.name === "node_modules") continue; + const full = path.join(dir, entry.name); + + if (entry.isDirectory()) { + if (EXCLUDE_SEGMENTS.has(entry.name)) continue; + if (/^\[.+\]$/.test(entry.name) || /^\(.+\)$/.test(entry.name)) { + if (/^\(.+\)$/.test(entry.name)) { + pages.push(...walkPages(full, segments)); + } + continue; + } + pages.push(...walkPages(full, [...segments, entry.name])); + continue; + } + + if ( + entry.name === "page.tsx" || + entry.name === "page.ts" || + entry.name === "page.jsx" || + entry.name === "page.js" + ) { + const routePath = segments.length ? `/${segments.join("/")}` : "/"; + pages.push(routePath); + } + } + + return pages; +} + +function main() { + const paths = [...new Set(walkPages(appDir))].sort((a, b) => { + if (a === "/") return -1; + if (b === "/") return 1; + return a.localeCompare(b); + }); + + const staticPages = paths.map((routePath) => { + const meta = PRIORITY[routePath] ?? { + changefreq: "monthly", + priority: 0.5, + }; + return { path: routePath, ...meta }; + }); + + const domain = websiteDomain().replace(/^https?:\/\//, "").replace(/\/+$/, ""); + + const config = { + baseUrl: `https://${domain}`, + staticPages, + // Mashinify public paths differ from Meshkee catalog defaults. + templates: { + userProduct: "/user-products/{slug}", + blog: "/blog/{slug}", + }, + }; + + fs.mkdirSync(path.dirname(outFile), { recursive: true }); + fs.writeFileSync(outFile, `${JSON.stringify(config, null, 2)}\n`, "utf8"); + console.log( + `Wrote ${path.relative(root, outFile)} (${staticPages.length} static pages)`, + ); +} + +main(); diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index 1ee507d..72d8bf7 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -15,9 +15,16 @@ export async function generateMetadata({ const { slug } = await params; const blog = await getBlogBySlugOrNull(slug); if (!blog) return { title: "مقاله یافت نشد" }; + const description = blog.abstract ?? blog.title; + const image = resolveBlogImage(blog); return { title: blog.title, - description: blog.abstract ?? blog.title, + description, + openGraph: { + title: blog.title, + description, + ...(image ? { images: [{ url: image, alt: blog.title }] } : {}), + }, }; } diff --git a/src/app/globals.css b/src/app/globals.css index c985b09..096f5e2 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -279,6 +279,163 @@ img { border-color: rgba(255, 255, 255, 0.8); } +/* Footer bottom — Meshkee credit (Safeteb / Havaran style) */ +.footer-bottom-bar { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: space-between; + padding: 4px 0; +} + +.footer-bottom-site { + color: #9c9c9c; + font-size: 12px; +} + +.meshkee-credit { + align-items: center; + color: #9c9c9c; + display: inline-flex; + gap: 10px; + padding: 6px 10px; + position: relative; + text-decoration: none; + transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit:hover { + color: #e0e0e0; + text-decoration: none; +} + +.meshkee-credit__frame { + inset: 0; + pointer-events: none; + position: absolute; +} + +.meshkee-credit__line { + background: #777; + position: absolute; + transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit__line--l { + height: 100%; + left: 0; + top: 0; + transform: scaleY(0); + transform-origin: top; + transition-delay: 0s; + width: 1px; +} + +.meshkee-credit__line--b { + bottom: 0; + height: 1px; + left: 0; + transform: scaleX(0); + transform-origin: left; + transition-delay: 0.14s; + width: 100%; +} + +.meshkee-credit__line--r { + height: 100%; + right: 0; + top: 0; + transform: scaleY(0); + transform-origin: bottom; + transition-delay: 0.28s; + width: 1px; +} + +.meshkee-credit__line--t { + height: 1px; + left: 0; + top: 0; + transform: scaleX(0); + transform-origin: right; + transition-delay: 0.42s; + width: 100%; +} + +.meshkee-credit:hover .meshkee-credit__line--l, +.meshkee-credit:hover .meshkee-credit__line--r { + transform: scaleY(1); +} + +.meshkee-credit:hover .meshkee-credit__line--t, +.meshkee-credit:hover .meshkee-credit__line--b { + transform: scaleX(1); +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--l { + transition-delay: 0.42s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--b { + transition-delay: 0.28s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--r { + transition-delay: 0.14s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--t { + transition-delay: 0s; +} + +.meshkee-credit__logo { + display: block; + flex-shrink: 0; + height: 42px; + width: auto; + aspect-ratio: 1 / 1; + object-fit: contain; + opacity: 0.7; + position: relative; + transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit:hover .meshkee-credit__logo { + opacity: 1; +} + +.meshkee-credit__text { + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1.25; + min-height: 42px; + position: relative; +} + +.meshkee-credit__label { + font-size: 11px; +} + +.meshkee-credit__name { + color: #cfcfcf; + font-size: 13px; + font-weight: 600; + transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit:hover .meshkee-credit__name { + color: #fff; +} + +@media only screen and (max-width: 575px) { + .footer-bottom-bar { + flex-direction: column; + justify-content: center; + text-align: center; + } +} + @media (prefers-reduced-motion: reduce) { .animate-rise, .animate-rise-delay-1, @@ -287,4 +444,8 @@ img { .search-shell:focus-within { animation: none; } + + .meshkee-credit__line { + transition: none; + } } diff --git a/src/app/meshkee/sitemap-config.json/route.ts b/src/app/meshkee/sitemap-config.json/route.ts new file mode 100644 index 0000000..998558b --- /dev/null +++ b/src/app/meshkee/sitemap-config.json/route.ts @@ -0,0 +1,17 @@ +import sitemapConfig from "@/lib/sitemap-config.generated.json"; + +const headers = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + "Cache-Control": "public, max-age=60", + "Content-Type": "application/json; charset=utf-8", +}; + +export async function OPTIONS() { + return new Response(null, { status: 204, headers }); +} + +export async function GET() { + return Response.json(sitemapConfig, { headers }); +} diff --git a/src/app/user-products/[slug]/page.tsx b/src/app/user-products/[slug]/page.tsx index e8a7f60..f3aa7e0 100644 --- a/src/app/user-products/[slug]/page.tsx +++ b/src/app/user-products/[slug]/page.tsx @@ -7,11 +7,11 @@ import { ProductTabs } from "@/components/ProductTabs"; import { Products } from "@/components/Products"; import { SiteShell } from "@/components/SiteShell"; import { site } from "@/data/home"; -import { getCategoryVariationFieldMap } from "@/lib/api/product-fields"; import { formatCondition, formatPrice, getUserProductBySlugOrNull, + getUserProductTechnicalInfo, listUserProducts, productCategory, productTitle, @@ -29,9 +29,17 @@ export async function generateMetadata({ const product = await getUserProductBySlugOrNull(slug); if (!product) return { title: "محصول یافت نشد" }; const title = productTitle(product); + const description = + product.descriptionFa || product.description || product.descriptionEn || title; + const image = product.imageUrl || product.images?.[0]?.url || undefined; return { title, - description: product.descriptionFa || product.description || title, + description, + openGraph: { + title, + description, + ...(image ? { images: [{ url: image, alt: title }] } : {}), + }, }; } @@ -50,16 +58,15 @@ export default async function UserProductDetailPage({ params }: PageProps) { const description = product.descriptionFa || product.description || product.descriptionEn; const deliveryNotes = product.deliveryNotes ?? product.deliveryNote ?? null; - const technicalFieldLabels = await getCategoryVariationFieldMap( - product.categoryId, - ); - - const similarResult = product.categoryId - ? await listUserProducts({ - categoryId: product.categoryId, - pageSize: 5, - }) - : { items: [], total: 0 }; + const [technicalInfo, similarResult] = await Promise.all([ + getUserProductTechnicalInfo(slug), + product.categoryId + ? listUserProducts({ + categoryId: product.categoryId, + pageSize: 5, + }) + : Promise.resolve({ items: [], total: 0 }), + ]); const similarProducts = similarResult.items .filter((item) => item.id !== product.id) @@ -149,7 +156,7 @@ export default async function UserProductDetailPage({ params }: PageProps) { technicalNotes={product.technicalNotes} deliveryNotes={deliveryNotes} technicalValues={product.technicalValues} - technicalFieldLabels={technicalFieldLabels} + technicalInfo={technicalInfo} /> {similarProducts.length > 0 ? ( diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 2596c8b..c2093fe 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -56,11 +56,42 @@ export function Footer() {
- diff --git a/src/components/PageBanner.tsx b/src/components/PageBanner.tsx index 1d90d04..a2da8c2 100644 --- a/src/components/PageBanner.tsx +++ b/src/components/PageBanner.tsx @@ -11,7 +11,7 @@ export function PageBanner({ kicker, title, description }: PageBannerProps) {
; + technicalInfo?: UserProductTechnicalInfo | null; }; type TabId = "description" | "technical" | "comments"; @@ -23,13 +25,28 @@ const tabMeta: Record< comments: { label: "نظرات", labelEn: "Comments" }, }; +function hasTechnicalValues(value: unknown) { + if (value === null || value === undefined) return false; + if (Array.isArray(value)) return value.length > 0; + if (typeof value === "object") { + return Object.keys(value as object).length > 0; + } + if (typeof value === "string") return Boolean(value.trim()); + return true; +} + export function ProductTabs({ description, technicalNotes, deliveryNotes, technicalValues, - technicalFieldLabels = {}, + technicalInfo = null, }: ProductTabsProps) { + const technicalRows = useMemo( + () => rowsFromTechnicalInfo(technicalInfo), + [technicalInfo], + ); + const tabs = useMemo(() => { const items: { id: TabId; label: string; labelEn: string }[] = []; @@ -40,12 +57,8 @@ export function ProductTabs({ const hasTechnical = Boolean(technicalNotes?.trim()) || Boolean(deliveryNotes?.trim()) || - (technicalValues !== null && - technicalValues !== undefined && - !(typeof technicalValues === "object" && - !Array.isArray(technicalValues) && - Object.keys(technicalValues as object).length === 0) && - !(Array.isArray(technicalValues) && technicalValues.length === 0)); + technicalRows.length > 0 || + hasTechnicalValues(technicalValues); if (hasTechnical) { items.push({ id: "technical", ...tabMeta.technical }); @@ -54,7 +67,13 @@ export function ProductTabs({ items.push({ id: "comments", ...tabMeta.comments }); return items; - }, [deliveryNotes, description, technicalNotes, technicalValues]); + }, [ + deliveryNotes, + description, + technicalNotes, + technicalRows.length, + technicalValues, + ]); const [activeTab, setActiveTab] = useState(tabs[0]?.id ?? "comments"); @@ -120,12 +139,10 @@ export function ProductTabs({
) : null} - {technicalValues ? ( - - ) : null} + {deliveryNotes ? (
@@ -141,8 +158,8 @@ export function ProductTabs({ ) : null} {activeTab === "comments" ? ( -
-
+
+

COMMENTS

diff --git a/src/components/TechnicalValuesTable.tsx b/src/components/TechnicalValuesTable.tsx index 4dcaaae..75344c5 100644 --- a/src/components/TechnicalValuesTable.tsx +++ b/src/components/TechnicalValuesTable.tsx @@ -1,18 +1,27 @@ import { normalizeTechnicalValueRows, + rowsFromTechnicalInfo, type TechnicalValueRow, } from "@/lib/product-technical-values"; +import type { UserProductTechnicalInfo } from "@/lib/api/user-products"; type TechnicalValuesTableProps = { - value: unknown; + value?: unknown; fieldLabels?: Record; + technicalInfo?: UserProductTechnicalInfo | null; }; export function TechnicalValuesTable({ value, fieldLabels = {}, + technicalInfo, }: TechnicalValuesTableProps) { - const rows = normalizeTechnicalValueRows(value, fieldLabels); + const fromInfo = rowsFromTechnicalInfo(technicalInfo); + const rows: TechnicalValueRow[] = + fromInfo.length > 0 + ? fromInfo + : normalizeTechnicalValueRows(value, fieldLabels); + if (rows.length === 0) return null; return ( @@ -34,7 +43,12 @@ export function TechnicalValuesTable({ {rows.map((row) => ( - + + + {row.label || "—"} + + {row.value} + ))} @@ -42,14 +56,3 @@ export function TechnicalValuesTable({
); } - -function TechnicalValueTableRow({ row }: { row: TechnicalValueRow }) { - return ( - - - {row.label || "—"} - - {row.value} - - ); -} diff --git a/src/lib/api/product-fields.ts b/src/lib/api/product-fields.ts deleted file mode 100644 index b669630..0000000 --- a/src/lib/api/product-fields.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { apiGet } from "./client"; -import { findCategoryById, listCategories } from "./categories"; - -export type CategoryVariationField = { - id: string; - name: string; - nameFa: string | null; - sortOrder?: number; -}; - -function variationFieldLabel( - field: Pick, -) { - return field.nameFa || field.name; -} - -export async function listCategoryVariationFields(categoryId: string) { - try { - const data = await apiGet<{ items: CategoryVariationField[] }>( - "/product-category-variation-fields", - { categoryId }, - ); - return data.items ?? []; - } catch (error) { - console.error("listCategoryVariationFields failed", error); - return [] as CategoryVariationField[]; - } -} - -export async function getCategoryVariationFieldMap(categoryId?: string | null) { - if (!categoryId) return {}; - - const categories = await listCategories("product"); - const category = findCategoryById(categories, categoryId); - const categoryIds = [categoryId, category?.parentId].filter(Boolean) as string[]; - - for (const id of categoryIds) { - const fields = await listCategoryVariationFields(id); - if (fields.length > 0) { - return Object.fromEntries( - fields.map((field) => [field.id, variationFieldLabel(field)]), - ); - } - } - - return {}; -} diff --git a/src/lib/api/user-products.ts b/src/lib/api/user-products.ts index 435fd0a..6201150 100644 --- a/src/lib/api/user-products.ts +++ b/src/lib/api/user-products.ts @@ -18,6 +18,42 @@ export type ListUserProductsParams = { promoted?: boolean; }; +export type TechnicalFormFieldOption = { + id: string; + label?: string | null; + value?: string | null; + sortOrder?: number; +}; + +export type TechnicalFormField = { + id: string; + label: string; + key?: string | null; + type?: string | null; + isRequired?: boolean; + sortOrder?: number; + options?: TechnicalFormFieldOption[]; +}; + +export type TechnicalForm = { + id?: string; + categoryId?: string; + fields?: TechnicalFormField[]; +} | null; + +export type TechnicalInfoValue = { + fieldId: string; + textValue?: string | null; + numberValue?: number | string | null; + optionId?: string | null; + optionIds?: string[] | null; +}; + +export type UserProductTechnicalInfo = { + form: TechnicalForm; + values: TechnicalInfoValue[]; +}; + const emptyPage = (page = 1, pageSize = 12): Paginated => ({ items: [], total: 0, @@ -61,6 +97,32 @@ export async function getUserProductBySlugOrNull(slug: string) { } } +/** Labels + values for specs table. Join form.fields[].id ↔ values[].fieldId. */ +export async function getUserProductTechnicalInfo(slug: string) { + try { + return await apiGet( + `/user-products/${encodeURIComponent(slug)}/technical-info`, + ); + } catch (error) { + if (error instanceof ApiError && error.status === 404) { + return { form: null, values: [] } satisfies UserProductTechnicalInfo; + } + console.error("getUserProductTechnicalInfo failed", error); + return { form: null, values: [] } satisfies UserProductTechnicalInfo; + } +} + +export function technicalFieldLabelMap( + info: UserProductTechnicalInfo | null | undefined, +): Record { + const fields = info?.form?.fields ?? []; + return Object.fromEntries( + fields + .filter((field) => field.id && field.label) + .map((field) => [String(field.id), field.label]), + ); +} + export const conditionLabels: Record = { new: "نو", stock: "کارکرده", diff --git a/src/lib/product-technical-values.ts b/src/lib/product-technical-values.ts index 7380f7e..2d901e7 100644 --- a/src/lib/product-technical-values.ts +++ b/src/lib/product-technical-values.ts @@ -1,7 +1,15 @@ +import type { + TechnicalFormField, + TechnicalInfoValue, + UserProductTechnicalInfo, +} from "@/lib/api/user-products"; + export type TechnicalValueItem = { fieldId?: string | number | null; textValue?: string | null; numberValue?: number | string | null; + optionId?: string | null; + optionIds?: string[] | null; value?: string | number | null; fieldName?: string | null; fieldNameFa?: string | null; @@ -20,6 +28,7 @@ export type TechnicalValueRow = { id: string; label: string; value: string; + sortOrder: number; }; function pickString(...values: Array) { @@ -48,6 +57,71 @@ function parseJsonObject(value: string) { } } +function resolveOptionLabel( + field: TechnicalFormField | undefined, + optionId: string | null | undefined, +) { + if (!field || !optionId) return ""; + const option = field.options?.find((item) => String(item.id) === String(optionId)); + return pickString(option?.label, option?.value, optionId); +} + +function resolveValueText( + value: TechnicalInfoValue | TechnicalValueItem, + field?: TechnicalFormField, +) { + const optionIds = + value.optionIds?.filter(Boolean).map(String) ?? + (value.optionId ? [String(value.optionId)] : []); + + if (optionIds.length > 0) { + const labels = optionIds + .map((id) => resolveOptionLabel(field, id)) + .filter(Boolean); + if (labels.length > 0) return labels.join("، "); + } + + return pickString( + formatValue(value.textValue), + formatValue(value.numberValue), + formatValue("value" in value ? value.value : undefined), + ); +} + +/** Preferred path: join technical-info form fields with submitted values. */ +export function rowsFromTechnicalInfo( + info: UserProductTechnicalInfo | null | undefined, +): TechnicalValueRow[] { + const fields = info?.form?.fields ?? []; + const values = info?.values ?? []; + if (fields.length === 0 || values.length === 0) return []; + + const fieldById = new Map(fields.map((field) => [String(field.id), field])); + const sortById = new Map( + fields.map((field, index) => [ + String(field.id), + field.sortOrder ?? index, + ]), + ); + + return values + .map((value, index) => { + const id = String(value.fieldId ?? `item-${index}`); + const field = fieldById.get(id); + const rowValue = resolveValueText(value, field); + if (!rowValue) return null; + + return { + id, + label: pickString(field?.label) || "", + value: rowValue, + sortOrder: sortById.get(id) ?? index, + }; + }) + .filter((row): row is TechnicalValueRow => Boolean(row)) + .sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label, "fa")); +} + export function normalizeTechnicalValueRows( value: unknown, fieldLabels: Record = {}, @@ -59,7 +133,7 @@ export function normalizeTechnicalValueRows( if (parsed !== null) return normalizeTechnicalValueRows(parsed, fieldLabels); const text = value.trim(); - return text ? [{ id: "text", label: "", value: text }] : []; + return text ? [{ id: "text", label: "", value: text, sortOrder: 0 }] : []; } if (Array.isArray(value)) { @@ -75,6 +149,7 @@ export function normalizeTechnicalValueRows( id: `item-${index}`, label: "", value: item.trim(), + sortOrder: index, }; } @@ -96,29 +171,26 @@ export function normalizeTechnicalValueRows( id ? fieldLabels[id] : undefined, ) || ""; - const rowValue = pickString( - formatValue(field.textValue), - formatValue(field.numberValue), - formatValue(field.value), - ); + const rowValue = resolveValueText(field); if (!rowValue) return null; - return { id, label, value: rowValue }; + return { id, label, value: rowValue, sortOrder: index }; }) .filter((row): row is TechnicalValueRow => Boolean(row)); } if (typeof value === "object") { return Object.entries(value as Record) - .map(([key, val]) => ({ + .map(([key, val], index) => ({ id: key, label: fieldLabels[key] || key, value: formatValue(val), + sortOrder: index, })) .filter((row) => row.value); } const text = formatValue(value); - return text ? [{ id: "value", label: "", value: text }] : []; + return text ? [{ id: "value", label: "", value: text, sortOrder: 0 }] : []; } diff --git a/src/lib/sitemap-config.generated.json b/src/lib/sitemap-config.generated.json new file mode 100644 index 0000000..d09a0e4 --- /dev/null +++ b/src/lib/sitemap-config.generated.json @@ -0,0 +1,39 @@ +{ + "baseUrl": "https://www.mashinify.com", + "staticPages": [ + { + "path": "/", + "changefreq": "daily", + "priority": 1 + }, + { + "path": "/about", + "changefreq": "monthly", + "priority": 0.6 + }, + { + "path": "/blog", + "changefreq": "weekly", + "priority": 0.8 + }, + { + "path": "/categories", + "changefreq": "weekly", + "priority": 0.8 + }, + { + "path": "/contact", + "changefreq": "monthly", + "priority": 0.5 + }, + { + "path": "/user-products", + "changefreq": "daily", + "priority": 0.9 + } + ], + "templates": { + "userProduct": "/user-products/{slug}", + "blog": "/blog/{slug}" + } +}