Initial commit: Mana Collection dashboards (Next.js).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-07 20:50:24 +03:30
co-authored by Cursor
commit 18b29f5b62
95 changed files with 16414 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
export type BlogPost = {
id: string;
title: string;
date: string;
author: string;
abstract: string;
body: string;
image: string;
tags: string[];
metaTitle: string;
metaDescription: string;
metaKeywords: string;
};
export const blogPosts: BlogPost[] = [
{
id: "bl1",
title: "زیبایی در سادگی",
date: "2026-03-12",
author: "سارا محمدی",
abstract: "نگاهی به مینیمالیسم در طراحی لباس و نقش خطوط آرام در کالکشن مانا.",
body: "<p>سادگی فقط حذف نیست؛ انتخاب آگاهانه است. در این مطلب درباره بافت، رنگ و فرم صحبت می‌کنیم.</p>",
image: "/images/ahmad-ebadi-r5IzkiO08JU-unsplash.jpg",
tags: ["مینیمال", "طراحی"],
metaTitle: "زیبایی در سادگی | مانا",
metaDescription: "نگاهی به مینیمالیسم در طراحی لباس",
metaKeywords: "مینیمال، لباس، مانا",
},
{
id: "bl2",
title: "پارچه‌های فصل بهار",
date: "2026-02-28",
author: "نیلوفر احمدی",
abstract: "انتخاب پارچه مناسب برای هوای ملایم بهار و ترکیب رنگ‌های خنثی.",
body: "<p>بهار فرصت بازی با بافت‌های سبک است. لینن، پنبه نرم و لایه‌های شفاف.</p>",
image: "/images/jonathan-borba-XJt51hAa3z8-unsplash.jpg",
tags: ["بهار", "پارچه"],
metaTitle: "پارچه‌های فصل بهار | مانا",
metaDescription: "راهنمای انتخاب پارچه برای بهار",
metaKeywords: "بهار، پارچه، کالکشن",
},
{
id: "bl3",
title: "استایل روزمره شیک",
date: "2026-01-18",
author: "مریم کریمی",
abstract: "چگونه با چند قطعه پایه، استایلی مرتب و ماندگار بسازید.",
body: "<p>قطعات پایه مثل مانتو کلاسیک و پیراهن ساده، ستون استایل روزمره‌اند.</p>",
image: "/images/aiony-haust-owp8uQgoK8U-unsplash.jpg",
tags: ["استایل", "روزمره"],
metaTitle: "استایل روزمره شیک | مانا",
metaDescription: "ساخت استایل روزمره با قطعات پایه",
metaKeywords: "استایل، روزمره",
},
{
id: "bl4",
title: "رنگ‌های خنثی در کمد",
date: "2025-12-05",
author: "الهام رضایی",
abstract: "چرا پالت مشکی، سفید و بژ همیشه کار می‌کند و چگونه ترکیب شود.",
body: "<p>رنگ‌های خنثی فضای تنفس به استایل می‌دهند و ترکیب آن‌ها آسان است.</p>",
image: "/images/ola-szkolda-J61EvbE1UCE-unsplash.jpg",
tags: ["رنگ", "کمد"],
metaTitle: "رنگ‌های خنثی در کمد | مانا",
metaDescription: "کار با پالت خنثی در استایل",
metaKeywords: "رنگ، مشکی، بژ",
},
];
export function formatBlogDate(isoDate: string) {
const date = new Date(`${isoDate}T12:00:00`);
return new Intl.DateTimeFormat("fa-IR-u-ca-persian", {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(date);
}
+436
View File
@@ -0,0 +1,436 @@
export type Brand = {
id: string;
nameFa: string;
nameEn: string;
image: string;
};
export type CategoryVariants = {
colors: string[];
sizes: string[];
packages: Array<"6" | "12">;
};
export type CategoryNode = {
id: string;
nameFa: string;
nameEn: string;
children?: CategoryNode[];
variants?: CategoryVariants;
};
export type ProductVariantPrice = {
id: string;
colorId: string;
sizeId: string;
packageId: string;
price: string;
discountPrice?: string;
};
export type Product = {
id: string;
nameFa: string;
nameEn: string;
summary?: string;
description?: string;
categoryId: string;
brandId: string;
price: number;
discountPrice?: number;
image: string;
gallery?: string[];
tags: string[];
variantPrices?: ProductVariantPrice[];
};
/** Mock DB catalogs for category variants */
export const colorCatalog = [
{ id: "black", nameFa: "مشکی", hex: "#0a0a0a" },
{ id: "white", nameFa: "سفید", hex: "#f5f5f5" },
{ id: "cream", nameFa: "کرم", hex: "#e8dcc8" },
{ id: "beige", nameFa: "بژ", hex: "#c4a882" },
{ id: "navy", nameFa: "سرمه‌ای", hex: "#1b2a4a" },
{ id: "olive", nameFa: "زیتونی", hex: "#556b2f" },
{ id: "burgundy", nameFa: "زرشکی", hex: "#6b1e2a" },
{ id: "gray", nameFa: "طوسی", hex: "#8a8a8a" },
{ id: "brown", nameFa: "قهوه‌ای", hex: "#6b4423" },
{ id: "red", nameFa: "قرمز", hex: "#b91c1c" },
];
export const sizeCatalog = [
{ id: "xs", nameFa: "XS" },
{ id: "s", nameFa: "S" },
{ id: "m", nameFa: "M" },
{ id: "l", nameFa: "L" },
{ id: "xl", nameFa: "XL" },
{ id: "xxl", nameFa: "XXL" },
{ id: "36", nameFa: "36" },
{ id: "38", nameFa: "38" },
{ id: "40", nameFa: "40" },
{ id: "42", nameFa: "42" },
{ id: "44", nameFa: "44" },
];
export const packageCatalog: Array<{ id: "6" | "12"; nameFa: string }> = [
{ id: "6", nameFa: "۶ تایی" },
{ id: "12", nameFa: "۱۲ تایی" },
];
export const emptyVariants = (): CategoryVariants => ({
colors: [],
sizes: [],
packages: [],
});
export const brands: Brand[] = [
{
id: "b1",
nameFa: "مانا",
nameEn: "Mana",
image: "/images/ola-szkolda-J61EvbE1UCE-unsplash.jpg",
},
{
id: "b2",
nameFa: "نویر",
nameEn: "Noir",
image: "/images/aiony-haust-owp8uQgoK8U-unsplash.jpg",
},
{
id: "b3",
nameFa: "لومن",
nameEn: "Lumen",
image: "/images/jonathan-borba-XJt51hAa3z8-unsplash.jpg",
},
{
id: "b4",
nameFa: "اتلیه",
nameEn: "Atelier",
image: "/images/ahmad-ebadi-r5IzkiO08JU-unsplash.jpg",
},
];
export const categories: CategoryNode[] = [
{
id: "c1",
nameFa: "زنانه",
nameEn: "Women",
children: [
{
id: "c1-1",
nameFa: "مانتو",
nameEn: "Manteau",
variants: {
colors: ["black", "cream", "navy", "gray"],
sizes: ["s", "m", "l", "xl"],
packages: ["6"],
},
children: [
{ id: "c1-1-1", nameFa: "بهاره", nameEn: "Spring" },
{ id: "c1-1-2", nameFa: "پاییزه", nameEn: "Autumn" },
],
},
{
id: "c1-2",
nameFa: "پیراهن",
nameEn: "Dress",
variants: {
colors: ["white", "olive", "red", "beige"],
sizes: ["36", "38", "40", "42"],
packages: ["6", "12"],
},
},
{
id: "c1-3",
nameFa: "شلوار",
nameEn: "Trousers",
variants: {
colors: ["black", "beige", "olive"],
sizes: ["38", "40", "42", "44"],
packages: [],
},
},
],
},
{
id: "c2",
nameFa: "مردانه",
nameEn: "Men",
children: [
{ id: "c2-1", nameFa: "کت", nameEn: "Jacket" },
{ id: "c2-2", nameFa: "پیراهن رسمی", nameEn: "Shirt" },
],
},
{
id: "c3",
nameFa: "اکسسوری",
nameEn: "Accessories",
children: [
{ id: "c3-1", nameFa: "کیف", nameEn: "Bag" },
{ id: "c3-2", nameFa: "کفش", nameEn: "Shoes" },
],
},
];
export const products: Product[] = [
{
id: "p1",
nameFa: "مانتو مشکی کلاسیک",
nameEn: "Classic Black Manteau",
categoryId: "c1-1",
brandId: "b1",
price: 4200000,
discountPrice: 3570000,
image: "/images/aiony-haust-owp8uQgoK8U-unsplash.jpg",
tags: ["کلاسیک", "مشکی"],
},
{
id: "p2",
nameFa: "پیراهن سفید مینیمال",
nameEn: "Minimal White Dress",
categoryId: "c1-2",
brandId: "b3",
price: 2800000,
image: "/images/jonathan-borba-XJt51hAa3z8-unsplash.jpg",
tags: ["مینیمال", "سفید"],
},
{
id: "p3",
nameFa: "کت پشمی طوسی",
nameEn: "Grey Wool Coat",
categoryId: "c2-1",
brandId: "b2",
price: 5100000,
discountPrice: 4590000,
image: "/images/ola-szkolda-J61EvbE1UCE-unsplash.jpg",
tags: ["زمستانه"],
},
{
id: "p4",
nameFa: "شلوار بگ کرم",
nameEn: "Cream Baggy Pants",
categoryId: "c1-3",
brandId: "b1",
price: 1950000,
image: "/images/chyntia-juls-HlVjI5WmoQY-unsplash.jpg",
tags: ["روزمره"],
},
{
id: "p5",
nameFa: "بلوز ابریشمی زیتونی",
nameEn: "Olive Silk Blouse",
categoryId: "c1-2",
brandId: "b4",
price: 2400000,
discountPrice: 1920000,
image: "/images/valerie-elash-T5tIoXFJfSk-unsplash.jpg",
tags: ["ابریشم"],
},
{
id: "p6",
nameFa: "دامن پلیسه مشکی",
nameEn: "Black Pleated Skirt",
categoryId: "c1-2",
brandId: "b2",
price: 1680000,
image: "/images/eve-maier-u1OuYQa0WtQ-unsplash.jpg",
tags: ["مجلسی"],
},
{
id: "p7",
nameFa: "کت جین روشن",
nameEn: "Light Denim Jacket",
categoryId: "c2-1",
brandId: "b3",
price: 3200000,
discountPrice: 2880000,
image: "/images/amir-asghari-xoEb3kS_YoM-unsplash.jpg",
tags: ["جین"],
},
{
id: "p8",
nameFa: "پیراهن زرد ابریشمی",
nameEn: "Yellow Silk Dress",
categoryId: "c1-2",
brandId: "b4",
price: 3900000,
image: "/images/ryanwar-hanif-98XENwtPMCc-unsplash.jpg",
tags: ["رنگارنگ"],
},
];
export function formatPrice(value: number) {
return new Intl.NumberFormat("en-US").format(value);
}
export function discountPercent(price: number, discountPrice: number) {
return Math.round(((price - discountPrice) / price) * 100);
}
export function flattenCategories(
nodes: CategoryNode[],
prefix = "",
): { id: string; label: string }[] {
return nodes.flatMap((node) => {
const label = prefix ? `${prefix} / ${node.nameFa}` : node.nameFa;
return [
{ id: node.id, label },
...(node.children ? flattenCategories(node.children, label) : []),
];
});
}
export function findCategoryNode(
nodes: CategoryNode[],
id: string,
): CategoryNode | null {
for (const node of nodes) {
if (node.id === id) return node;
if (node.children?.length) {
const found = findCategoryNode(node.children, id);
if (found) return found;
}
}
return null;
}
/** Resolve variants from category or nearest parent that has them */
export function resolveCategoryVariants(
categoryId: string,
nodes: CategoryNode[] = categories,
): CategoryVariants | null {
let current = findCategoryNode(nodes, categoryId);
while (current) {
if (
current.variants &&
(current.variants.colors.length ||
current.variants.sizes.length ||
current.variants.packages.length)
) {
return current.variants;
}
const parent = findParentCategory(nodes, current.id);
if (!parent) break;
current = parent;
}
return null;
}
function findParentCategory(
nodes: CategoryNode[],
childId: string,
): CategoryNode | null {
for (const node of nodes) {
if (node.children?.some((c) => c.id === childId)) return node;
if (node.children?.length) {
const found = findParentCategory(node.children, childId);
if (found) return found;
}
}
return null;
}
export function getCategoryVariantOptions(
categoryId: string,
nodes: CategoryNode[] = categories,
) {
const groups = getCategoryVariantGroups(categoryId, nodes);
return [
...groups.colors.map((opt) => ({
value: `color:${opt.value}`,
label: `رنگ · ${opt.label}`,
})),
...groups.sizes.map((opt) => ({
value: `size:${opt.value}`,
label: `سایز · ${opt.label}`,
})),
...groups.packages.map((opt) => ({
value: `package:${opt.value}`,
label: `پکیج · ${opt.label}`,
})),
];
}
export function getCategoryVariantGroups(
categoryId: string,
nodes: CategoryNode[] = categories,
) {
const variants = resolveCategoryVariants(categoryId, nodes);
const empty = {
colors: [] as { value: string; label: string }[],
sizes: [] as { value: string; label: string }[],
packages: [] as { value: string; label: string }[],
};
if (!variants) return empty;
return {
colors: variants.colors
.map((id) => {
const color = colorCatalog.find((item) => item.id === id);
return color ? { value: id, label: color.nameFa } : null;
})
.filter(Boolean) as { value: string; label: string }[],
sizes: variants.sizes
.map((id) => {
const size = sizeCatalog.find((item) => item.id === id);
return size ? { value: id, label: size.nameFa } : null;
})
.filter(Boolean) as { value: string; label: string }[],
packages: variants.packages
.map((id) => {
const pack = packageCatalog.find((item) => item.id === id);
return pack
? { value: id, label: `${pack.nameFa} (${id} pcs)` }
: null;
})
.filter(Boolean) as { value: string; label: string }[],
};
}
export function getVariantLabel(
row: Pick<ProductVariantPrice, "colorId" | "sizeId" | "packageId">,
categoryId: string,
nodes: CategoryNode[] = categories,
) {
const groups = getCategoryVariantGroups(categoryId, nodes);
const parts: string[] = [];
if (row.colorId) {
const color = groups.colors.find((opt) => opt.value === row.colorId);
if (color) parts.push(`رنگ · ${color.label}`);
}
if (row.sizeId) {
const size = groups.sizes.find((opt) => opt.value === row.sizeId);
if (size) parts.push(`سایز · ${size.label}`);
}
if (row.packageId) {
const pack = groups.packages.find((opt) => opt.value === row.packageId);
if (pack) parts.push(`پکیج · ${pack.label}`);
}
return parts.length ? parts.join(" / ") : "بدون تنوع";
}
export function findCategoryPath(
nodes: CategoryNode[],
id: string,
trail: string[] = [],
): string[] | null {
for (const node of nodes) {
const next = [...trail, node.nameFa];
if (node.id === id) return next;
if (node.children?.length) {
const found = findCategoryPath(node.children, id, next);
if (found) return found;
}
}
return null;
}
export function categoryMatchesQuery(node: CategoryNode, query: string): boolean {
const q = query.trim().toLowerCase();
if (!q) return true;
const self =
node.nameFa.includes(query.trim()) ||
node.nameEn.toLowerCase().includes(q);
if (self) return true;
return Boolean(node.children?.some((child) => categoryMatchesQuery(child, query)));
}
+81
View File
@@ -0,0 +1,81 @@
export type SubscriptionStatus = "regular" | "premium";
export type Customer = {
id: string;
nameFa: string;
phone: string;
joinedAt: string;
subscription: SubscriptionStatus;
};
export const customers: Customer[] = [
{
id: "cu1",
nameFa: "سارا محمدی",
phone: "09121234567",
joinedAt: "2025-11-12",
subscription: "premium",
},
{
id: "cu2",
nameFa: "نیلوفر احمدی",
phone: "09351239876",
joinedAt: "2026-01-04",
subscription: "regular",
},
{
id: "cu3",
nameFa: "مریم کریمی",
phone: "09197654321",
joinedAt: "2025-08-21",
subscription: "premium",
},
{
id: "cu4",
nameFa: "الهام رضایی",
phone: "09014567890",
joinedAt: "2026-02-18",
subscription: "regular",
},
{
id: "cu5",
nameFa: "هانیه موسوی",
phone: "09128887766",
joinedAt: "2025-12-02",
subscription: "regular",
},
{
id: "cu6",
nameFa: "یاسمن نوری",
phone: "09361112233",
joinedAt: "2026-03-09",
subscription: "premium",
},
{
id: "cu7",
nameFa: "زهرا حسینی",
phone: "09201230000",
joinedAt: "2025-06-30",
subscription: "regular",
},
{
id: "cu8",
nameFa: "فرناز اکبری",
phone: "09193456789",
joinedAt: "2026-04-01",
subscription: "premium",
},
];
export function formatJoinedDate(isoDate: string) {
const date = new Date(`${isoDate}T12:00:00`);
return new Intl.DateTimeFormat("fa-IR-u-ca-persian", {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(date);
}
export function subscriptionLabel(status: SubscriptionStatus) {
return status === "premium" ? "پرمیوم" : "عادی";
}