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;
};