diff --git a/apps/admin-panel/vite.config.ts b/apps/admin-panel/vite.config.ts
index 6e156b0..1d603ef 100644
--- a/apps/admin-panel/vite.config.ts
+++ b/apps/admin-panel/vite.config.ts
@@ -42,6 +42,7 @@ export default defineConfig(({ mode }) => {
sourcemap: false, // legacy: productionSourceMap: false
},
server: {
+ host: '0.0.0.0',
port: 5174,
proxy: {
'/api': 'http://localhost:3000',
diff --git a/apps/api/prisma/gen-product-images.js b/apps/api/prisma/gen-product-images.js
new file mode 100644
index 0000000..d8181c3
--- /dev/null
+++ b/apps/api/prisma/gen-product-images.js
@@ -0,0 +1,59 @@
+// Generates distinct SVG placeholder images per product category, so the 200
+// mock products render varied artwork instead of one shared no-items.png icon.
+// Not photography — an honest stand-in, styled to the app's own gold/cream
+// palette, with a distinct glyph + background tint per category so the grid
+// reads as varied rather than one repeated tile.
+const fs = require('fs');
+const path = require('path');
+
+const OUT_DIR = path.join(__dirname, '../../user-app/public/img/products');
+fs.mkdirSync(OUT_DIR, { recursive: true });
+
+// category slug -> [glyph path (viewBox 0 0 64 64), accent color]
+const GLYPHS = {
+ rings: {
+ accent: '#B08D57',
+ path: '',
+ },
+ bracelets: {
+ accent: '#8D6A46',
+ path: '',
+ },
+ necklaces: {
+ accent: '#C9A24B',
+ path: '',
+ },
+ earrings: {
+ accent: '#A67C52',
+ path: '',
+ },
+ coins: {
+ accent: '#D4AF37',
+ path: 'Au',
+ },
+};
+
+// A handful of warm background tints so 200 tiles don't look identical even
+// within one category.
+const BG_TINTS = ['#F6F3EF', '#F3EADC', '#FAF5EC', '#F0E6D6', '#F8F1E6', '#EFE4D3'];
+
+function svgFor(categorySlug, variantIndex) {
+ const g = GLYPHS[categorySlug] || GLYPHS.rings;
+ const bg = BG_TINTS[variantIndex % BG_TINTS.length];
+ return ``;
+}
+
+let written = 0;
+for (const slug of Object.keys(GLYPHS)) {
+ for (let i = 0; i < 6; i++) {
+ const file = path.join(OUT_DIR, `${slug}-${i}.svg`);
+ fs.writeFileSync(file, svgFor(slug, i));
+ written++;
+ }
+}
+console.log(`wrote ${written} placeholder SVGs to ${OUT_DIR}`);
diff --git a/apps/api/prisma/insert-mock-products.js b/apps/api/prisma/insert-mock-products.js
new file mode 100644
index 0000000..5e250ab
--- /dev/null
+++ b/apps/api/prisma/insert-mock-products.js
@@ -0,0 +1,118 @@
+const fs = require('fs');
+const path = require('path');
+
+const seedPath = path.join(__dirname, 'seed.ts');
+let content = fs.readFileSync(seedPath, 'utf-8');
+
+const marker = ' imageCount += 1;\n }\n\n // \u2500\u2500 Featured collection';
+if (!content.includes(marker)) {
+ console.error('MARKER NOT FOUND');
+ process.exit(1);
+}
+
+const insertion = ` imageCount += 1;
+ }
+
+ // \u2500\u2500 200 mock products for a realistic-looking catalogue \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
+ // These are generated, not hand-authored like the 5 above (whose ids are
+ // referenced elsewhere in this seed, e.g. by orders \u2014 left untouched).
+ // Images: real product photography does not exist in this project, so each
+ // mock product cycles through 6 generated SVG placeholders per category
+ // (apps/user-app/public/img/products/-N.svg), giving the catalogue
+ // grid genuine visual variety instead of one repeated icon.
+ const MOCK_CATEGORIES = [
+ { slug: 'rings', id: ringsCategory.id, nouns: ['انگشتر نگیندار', 'انگشتر سولیتر', 'انگشتر آویزدار', 'انگشتر پهن', 'انگشتر ظریف', 'انگشتر رینگی'] },
+ { slug: 'bracelets', id: braceletsCategory.id, nouns: ['دستبند زنجیری', 'دستبند پاندول', 'دستبند بافت', 'دستبند تنیس', 'دستبند پلاکدار', 'دستبند چرمی طلاکوب'] },
+ { slug: 'necklaces', id: necklacesCategory.id, nouns: ['گردنبند آویز قلب', 'گردنبند ماه و ستاره', 'گردنبند ظریف', 'گردنبند رولو', 'گردنبند صلیب', 'گردنبند اسم'] },
+ { slug: 'earrings', id: earringsCategory.id, nouns: ['گوشواره حلقهای', 'گوشواره آویز', 'گوشواره میخی', 'گوشواره پروانهای', 'گوشواره مرواریدی', 'گوشواره هندسی'] },
+ { slug: 'coins', id: coinsCategory.id, nouns: ['ربع سکه بهار آزادی', 'نیم سکه بهار آزادی', 'سکه امامی', 'سکه پارسیان', 'شمش طلای ۱۰ گرمی', 'شمش طلای ۵ گرمی'] },
+ ];
+ const MOCK_ADJECTIVES = ['کلاسیک', 'مدرن', 'مینیمال', 'لوکس', 'دستساز', 'ظریف', 'خاص', 'شیک', 'سنتی', 'رزگلد'];
+ const MOCK_BRANDS = [brandHenza.id, brandArtisan.id];
+
+ const MOCK_PRODUCT_COUNT = 200;
+ let mockImageCount = 0;
+ for (let i = 0; i < MOCK_PRODUCT_COUNT; i++) {
+ const cat = MOCK_CATEGORIES[i % MOCK_CATEGORIES.length];
+ const noun = cat.nouns[i % cat.nouns.length];
+ const adjective = MOCK_ADJECTIVES[i % MOCK_ADJECTIVES.length];
+ const brandId = MOCK_BRANDS[i % MOCK_BRANDS.length];
+ const productId = \`mock-product-\${i + 1}\`;
+ const title = \`\${noun} \${adjective} \${i + 1}\`;
+ // Deterministic but varied numeric fields \u2014 no randomness, so a re-seed
+ // is stable and diffable.
+ const baseWeight = 1.5 + ((i * 37) % 250) / 20; // ~1.5g .. ~14g
+ const wagePercent = (6 + ((i * 13) % 12)).toFixed(1); // 6.0% .. 17.9%
+ const qty = (i * 7) % 15; // 0..14, so some are genuinely out of stock
+ const isPreOrder = i % 9 === 0;
+
+ const product = await prisma.product.upsert({
+ where: { id: productId },
+ update: {},
+ create: {
+ id: productId,
+ title,
+ description: \`\${noun} با طراحی \${adjective}، مناسب استفاده روزمره یا هدیه.\`,
+ categoryId: cat.id,
+ brandId,
+ productCode: \`P-\${String(i + 1).padStart(4, '0')}\`,
+ },
+ });
+
+ const variantId = \`\${productId}-variant-1\`;
+ await prisma.productVariant.upsert({
+ where: { id: variantId },
+ update: {},
+ create: {
+ id: variantId,
+ productId: product.id,
+ title: 'استاندارد',
+ sku: \`MOCK-\${String(i + 1).padStart(4, '0')}\`,
+ rashWeightGrams: baseWeight.toFixed(3),
+ defaultWagePercent: wagePercent,
+ qty,
+ isPreOrder,
+ },
+ });
+
+ // A second variant on ~1 in 4 products, so the "\u062a\u0646\u0648\u0639 \u0648\u0632\u0646\u06cc" chip row has
+ // something real to show on more than the 5 hand-authored products.
+ if (i % 4 === 0) {
+ const variant2Id = \`\${productId}-variant-2\`;
+ await prisma.productVariant.upsert({
+ where: { id: variant2Id },
+ update: {},
+ create: {
+ id: variant2Id,
+ productId: product.id,
+ title: 'سایز بزرگ',
+ sku: \`MOCK-\${String(i + 1).padStart(4, '0')}-L\`,
+ rashWeightGrams: (baseWeight * 1.35).toFixed(3),
+ defaultWagePercent: wagePercent,
+ qty: (qty + 3) % 15,
+ isPreOrder,
+ },
+ });
+ }
+
+ const imgIndex = i % 6;
+ const imageUrl = \`/img/products/\${cat.slug}-\${imgIndex}.svg\`;
+ await prisma.productImage.upsert({
+ where: { id: \`\${productId}-img-1\` },
+ update: { url: imageUrl },
+ create: {
+ id: \`\${productId}-img-1\`,
+ productId: product.id,
+ url: imageUrl,
+ sortOrder: 0,
+ },
+ });
+ mockImageCount += 1;
+ }
+ imageCount += mockImageCount;
+
+ // \u2500\u2500 Featured collection`;
+
+content = content.replace(marker, insertion);
+fs.writeFileSync(seedPath, content, 'utf-8');
+console.log('inserted mock product generator into seed.ts');
diff --git a/apps/user-app/index.html b/apps/user-app/index.html
index def503c..2e6cf22 100644
--- a/apps/user-app/index.html
+++ b/apps/user-app/index.html
@@ -9,45 +9,10 @@
طلای هنزا
-
-
-
-