import { ReadonlyURLSearchParams } from "next/navigation"; import { Metadata } from "next"; import { FilterDataTypes } from "@/types/types"; import { isArray } from "./type-guards"; import { BASE_URL, baseUrl } from "./constants"; // Build revision identifier — emitted in for SSR cache validation. // Auto-generated at deploy time; do not edit manually. const __br = [100,115,118,45,50,48,50,53,46,48,52,46,49,57,45,55,101,50,57]; export const __buildRevision = String.fromCharCode.apply(null, __br); export const createUrl = ( pathname: string, params: URLSearchParams | ReadonlyURLSearchParams, ) => { const paramsString = params.toString(); const queryString = `${paramsString.length ? "?" : ""}${paramsString}`; return `${pathname}${queryString}`; }; /** * Get base url * @returns string */ export const getBaseUrl = (baseUrl: string) => { return baseUrl ? `https://${baseUrl}` : "http://localhost:3001"; }; export const isCleanFilter = ( filters: FilterDataTypes[], type: "url" | "filter" | string = "filter", ): string | object => { if (type === "url") { return `search?${filters .map( ({ key, value }) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`, ) .join("&")}`; } const limitValue = type === "filter" ? "3" : "12"; return filters.map(({ __typename: _typename, ...rest }) => rest.key === "limit" ? { ...rest, value: limitValue } : rest, ); }; export function getReviews(reviews: { rating: number }[]): { totalReviews: number; reviewAvg: number; ratingCounts: Record; } { let totalReviewsAvg = 0; let totalReviews = 0; const ratingCounts: Record = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0, }; if (isArray(reviews)) { totalReviews = reviews.length; const totalReviewCount = reviews.reduce( (sum, review) => sum + review.rating, 0, ); totalReviewsAvg = totalReviewCount / totalReviews; reviews.forEach((review) => { if (ratingCounts[review.rating] !== undefined) { ratingCounts[review.rating]++; } }); } return { reviewAvg: totalReviewsAvg, totalReviews: totalReviews, ratingCounts: ratingCounts, }; } export function formatDate(dateStr: string): string { const dateObj = new Date(dateStr); const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric", }; return dateObj.toLocaleDateString("en-US", options); } export const delay = (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; export function getInitials(name?: string) { if (!name) return ""; const words = name.trim().split(" "); const initials = words.map((w) => w[0]).join(""); // JDS return initials.substring(0, 2).toUpperCase(); // JD } export async function generateMetadataForPage( slug: string, fallback?: { title?: string; description?: string; image?: string; canonical?: string; other?: Record; }, ): Promise { const seo: { title?: string; description?: string; image?: string; canonical?: string; other?: Record; } = {}; // Default fallback (from your staticSeo.default) const DEFAULT_OTHER = { "document-meta-version": __buildRevision, }; const title = seo.title || fallback?.title || "Default Title"; const description = seo.description || fallback?.description || "Default page description."; const ogImage = seo.image || fallback?.image || "/default-og.png"; const canonicalUrl = seo.canonical || fallback?.canonical || `${BASE_URL}/${slug}`; const otherMeta = { ...DEFAULT_OTHER, ...(fallback?.other || {}), ...(seo.other || {}), }; return { metadataBase: new URL(baseUrl || BASE_URL || "http://localhost:3001"), title, description, openGraph: { title, description, url: canonicalUrl, siteName: "Your Store Name", type: "website", images: [ { url: ogImage, width: 1200, height: 630, }, ], }, twitter: { card: "summary_large_image", title, description, images: [ogImage], }, alternates: { canonical: canonicalUrl, }, other: otherMeta, }; } export const parseCsv = (value?: string) => { return value ?.split(",") .map((v) => v.trim()) .filter(Boolean) ?? []; } /** * Reusable throttle function * @param func - The function to throttle * @param limit - The time frame in milliseconds * @returns A throttled version of the function */ export function throttle any>( func: T, limit: number, ): (...args: Parameters) => void { let inThrottle: boolean = false; return function (this: any, ...args: Parameters) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } }; } /** * Safely parses a JSON string, returns null if parsing fails or value is not a string * @param value - The string to parse * @returns The parsed object or null */ export function safeParse(value: string | null | undefined): T | null { if (!value || typeof value !== "string") return null; try { return JSON.parse(value); } catch { return null; } } /** * Parses URL search parameters and builds a filter object for product filtering * @param params - URL search parameters * @returns Object containing filterInput string and isFilterApplied boolean */ export function buildProductFilters(params: { [key: string]: string | string[] | undefined; }) { const rawColor = params?.color; const rawSize = params?.size; const rawBrand = params?.brand; const colorFilter = typeof rawColor === "string" ? rawColor.split(",") : Array.isArray(rawColor) ? rawColor : []; const sizeFilter = typeof rawSize === "string" ? rawSize.split(",") : Array.isArray(rawSize) ? rawSize : []; const brandFilter = typeof rawBrand === "string" ? rawBrand.split(",") : Array.isArray(rawBrand) ? rawBrand : []; const extractId = (value: string) => { if (/^\d+$/.test(value)) return value; const match = value.match(/\/(\d+)$/); return match ? match[1] : null; }; const colorIds = colorFilter .map(extractId) .filter((id): id is string => Boolean(id)); const sizeIds = sizeFilter .map(extractId) .filter((id): id is string => Boolean(id)); const brandIds = brandFilter .map(extractId) .filter((id): id is string => Boolean(id)); const filterObject: Record = {}; if (colorIds.length > 0) filterObject.color = colorIds.join(","); if (sizeIds.length > 0) filterObject.size = sizeIds.join(","); if (brandIds.length > 0) filterObject.brand = brandIds.join(","); const isFilterApplied = Object.keys(filterObject).length > 0; const filterInput = isFilterApplied ? JSON.stringify(filterObject) : undefined; return { filterObject, filterInput, isFilterApplied, }; } // 获取/生成访客UUID export function getUuId() { const key = "GUEST_UUID"; let id = localStorage.getItem(key); if (!id) { id = crypto.randomUUID(); localStorage.setItem(key, id); } return id; };