@fayz-ai/shop 0.8.3 → 0.9.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog.js +2 -168
- package/dist/catalog.js.map +1 -1
- package/dist/{catalog.cjs → chunk-CTKZ6O2J.js} +11 -9
- package/dist/chunk-CTKZ6O2J.js.map +1 -0
- package/dist/chunk-IYQHKMLB.js +21 -0
- package/dist/chunk-IYQHKMLB.js.map +1 -0
- package/dist/chunk-JR25TISO.js +3 -0
- package/dist/chunk-JR25TISO.js.map +1 -0
- package/dist/{mock.cjs → chunk-SVTCJ6HP.js} +62 -103
- package/dist/chunk-SVTCJ6HP.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -579
- package/dist/index.js.map +1 -1
- package/dist/mock-catalog.d.ts.map +1 -1
- package/dist/mock-provider.d.ts +8 -1
- package/dist/mock-provider.d.ts.map +1 -1
- package/dist/mock.js +2 -476
- package/dist/mock.js.map +1 -1
- package/dist/provider.d.ts +5 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/runtime.js +3 -492
- package/dist/runtime.js.map +1 -1
- package/dist/supabase-provider.d.ts +5 -1
- package/dist/supabase-provider.d.ts.map +1 -1
- package/dist/tables.d.ts +1 -0
- package/dist/tables.d.ts.map +1 -1
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +22 -26
- package/src/index.ts +1 -0
- package/src/mock-catalog.ts +8 -0
- package/src/mock-provider.ts +63 -5
- package/src/provider.ts +8 -0
- package/src/supabase-provider.ts +64 -1
- package/src/tables.ts +1 -0
- package/src/types.ts +67 -0
- package/dist/catalog.cjs.map +0 -1
- package/dist/index.cjs +0 -1250
- package/dist/index.cjs.map +0 -1
- package/dist/mock.cjs.map +0 -1
- package/dist/provider.cjs +0 -4
- package/dist/provider.cjs.map +0 -1
- package/dist/runtime.cjs +0 -498
- package/dist/runtime.cjs.map +0 -1
- package/dist/types.cjs +0 -4
- package/dist/types.cjs.map +0 -1
package/src/provider.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
CustomerAddress, ShippingQuoteOption,
|
|
10
10
|
ShippingZone, CreateShippingZoneInput, UpdateShippingZoneInput,
|
|
11
11
|
CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions,
|
|
12
|
+
SalesChannel, CreateSalesChannelInput, UpdateSalesChannelInput,
|
|
12
13
|
} from './types'
|
|
13
14
|
|
|
14
15
|
export interface ShopProvider {
|
|
@@ -106,4 +107,11 @@ export interface ShopProvider {
|
|
|
106
107
|
createDiscount(input: CreateDiscountInput): Promise<Discount>
|
|
107
108
|
updateDiscount(id: string, input: UpdateDiscountInput): Promise<Discount>
|
|
108
109
|
deleteDiscount(id: string): Promise<void>
|
|
110
|
+
|
|
111
|
+
// Sales channels — the store's points of sale (Loja Online, WhatsApp, …).
|
|
112
|
+
// Optional: a provider without them yields no "Canais de venda" section.
|
|
113
|
+
listSalesChannels?(): Promise<SalesChannel[]>
|
|
114
|
+
createSalesChannel?(input: CreateSalesChannelInput): Promise<SalesChannel>
|
|
115
|
+
updateSalesChannel?(id: string, input: UpdateSalesChannelInput): Promise<SalesChannel>
|
|
116
|
+
deleteSalesChannel?(id: string): Promise<void>
|
|
109
117
|
}
|
package/src/supabase-provider.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
CreateOrderInput, UpdateOrderInput, ListOrdersOptions, PlaceOrderInput,
|
|
11
11
|
CreateCustomerInput, UpdateCustomerInput, ListCustomersOptions, ResolveCustomerInput,
|
|
12
12
|
CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions, OrderItem,
|
|
13
|
+
SalesChannel, CreateSalesChannelInput, UpdateSalesChannelInput,
|
|
13
14
|
ShippingZone, CreateShippingZoneInput, UpdateShippingZoneInput } from './types'
|
|
14
15
|
|
|
15
16
|
function getDb(): any {
|
|
@@ -195,6 +196,10 @@ function rowToDiscount(row: any): Discount {
|
|
|
195
196
|
value: row.value ?? 0,
|
|
196
197
|
usageLimit: row.usage_limit,
|
|
197
198
|
oncePerCustomer: row.once_per_customer ?? false,
|
|
199
|
+
minSubtotal: row.min_subtotal ?? null,
|
|
200
|
+
appliesTo: row.applies_to ?? 'all',
|
|
201
|
+
productIds: row.product_ids ?? [],
|
|
202
|
+
categoryIds: row.category_ids ?? [],
|
|
198
203
|
startsAt: row.starts_at,
|
|
199
204
|
endsAt: row.ends_at,
|
|
200
205
|
status: row.status ?? 'active',
|
|
@@ -204,6 +209,20 @@ function rowToDiscount(row: any): Discount {
|
|
|
204
209
|
}
|
|
205
210
|
}
|
|
206
211
|
|
|
212
|
+
function rowToSalesChannel(row: any): SalesChannel {
|
|
213
|
+
return {
|
|
214
|
+
id: row.id,
|
|
215
|
+
tenantId: row.tenant_id,
|
|
216
|
+
kind: row.kind,
|
|
217
|
+
label: row.label,
|
|
218
|
+
url: row.url,
|
|
219
|
+
enabled: row.enabled ?? true,
|
|
220
|
+
position: row.position ?? 0,
|
|
221
|
+
createdAt: row.created_at,
|
|
222
|
+
updatedAt: row.updated_at,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
207
226
|
export class SupabaseShopProvider implements ShopProvider {
|
|
208
227
|
// ---------------------------------------------------------------------------
|
|
209
228
|
// Products
|
|
@@ -660,7 +679,7 @@ export class SupabaseShopProvider implements ShopProvider {
|
|
|
660
679
|
async createDiscount(input: CreateDiscountInput): Promise<Discount> {
|
|
661
680
|
const { data, error } = await getDb()
|
|
662
681
|
.from(T.discounts)
|
|
663
|
-
.insert({ tenant_id: getTenantId(), title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? 'code', value: input.value, usage_limit: input.usageLimit ?? null, once_per_customer: input.oncePerCustomer ?? false, starts_at: input.startsAt ?? new Date().toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? 'active', times_used: 0 })
|
|
682
|
+
.insert({ tenant_id: getTenantId(), title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? 'code', value: input.value, usage_limit: input.usageLimit ?? null, once_per_customer: input.oncePerCustomer ?? false, min_subtotal: input.minSubtotal ?? null, applies_to: input.appliesTo ?? 'all', product_ids: input.productIds ?? [], category_ids: input.categoryIds ?? [], starts_at: input.startsAt ?? new Date().toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? 'active', times_used: 0 })
|
|
664
683
|
.select().single()
|
|
665
684
|
if (error) throw error
|
|
666
685
|
return rowToDiscount(data)
|
|
@@ -673,6 +692,10 @@ export class SupabaseShopProvider implements ShopProvider {
|
|
|
673
692
|
if (input.type !== undefined) updates.type = input.type
|
|
674
693
|
if (input.value !== undefined) updates.value = input.value
|
|
675
694
|
if (input.usageLimit !== undefined) updates.usage_limit = input.usageLimit
|
|
695
|
+
if (input.minSubtotal !== undefined) updates.min_subtotal = input.minSubtotal
|
|
696
|
+
if (input.appliesTo !== undefined) updates.applies_to = input.appliesTo
|
|
697
|
+
if (input.productIds !== undefined) updates.product_ids = input.productIds
|
|
698
|
+
if (input.categoryIds !== undefined) updates.category_ids = input.categoryIds
|
|
676
699
|
if (input.status !== undefined) updates.status = input.status
|
|
677
700
|
if (input.endsAt !== undefined) updates.ends_at = input.endsAt
|
|
678
701
|
const { data, error } = await getDb().from(T.discounts).update(updates).eq('id', id).select().single()
|
|
@@ -684,6 +707,46 @@ export class SupabaseShopProvider implements ShopProvider {
|
|
|
684
707
|
const { error } = await getDb().from(T.discounts).delete().eq('id', id)
|
|
685
708
|
if (error) throw error
|
|
686
709
|
}
|
|
710
|
+
|
|
711
|
+
// ---------------------------------------------------------------------------
|
|
712
|
+
// Sales channels
|
|
713
|
+
// ---------------------------------------------------------------------------
|
|
714
|
+
|
|
715
|
+
async listSalesChannels(): Promise<SalesChannel[]> {
|
|
716
|
+
const db = getDb()
|
|
717
|
+
const tenantId = getTenantId()
|
|
718
|
+
let q = db.from(T.salesChannels).select('*').order('position', { ascending: true })
|
|
719
|
+
if (tenantId) q = q.eq('tenant_id', tenantId)
|
|
720
|
+
const { data, error } = await q
|
|
721
|
+
if (error) throw error
|
|
722
|
+
return (data ?? []).map(rowToSalesChannel)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async createSalesChannel(input: CreateSalesChannelInput): Promise<SalesChannel> {
|
|
726
|
+
const { data, error } = await getDb()
|
|
727
|
+
.from(T.salesChannels)
|
|
728
|
+
.insert({ tenant_id: getTenantId(), kind: input.kind, label: input.label, url: input.url ?? null, enabled: input.enabled ?? true, position: input.position ?? 0 })
|
|
729
|
+
.select().single()
|
|
730
|
+
if (error) throw error
|
|
731
|
+
return rowToSalesChannel(data)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async updateSalesChannel(id: string, input: UpdateSalesChannelInput): Promise<SalesChannel> {
|
|
735
|
+
const updates: Record<string, unknown> = {}
|
|
736
|
+
if (input.kind !== undefined) updates.kind = input.kind
|
|
737
|
+
if (input.label !== undefined) updates.label = input.label
|
|
738
|
+
if (input.url !== undefined) updates.url = input.url
|
|
739
|
+
if (input.enabled !== undefined) updates.enabled = input.enabled
|
|
740
|
+
if (input.position !== undefined) updates.position = input.position
|
|
741
|
+
const { data, error } = await getDb().from(T.salesChannels).update(updates).eq('id', id).select().single()
|
|
742
|
+
if (error) throw error
|
|
743
|
+
return rowToSalesChannel(data)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async deleteSalesChannel(id: string): Promise<void> {
|
|
747
|
+
const { error } = await getDb().from(T.salesChannels).delete().eq('id', id)
|
|
748
|
+
if (error) throw error
|
|
749
|
+
}
|
|
687
750
|
}
|
|
688
751
|
|
|
689
752
|
export function createSupabaseShopProvider(): ShopProvider {
|
package/src/tables.ts
CHANGED
|
@@ -9,6 +9,7 @@ export const T = {
|
|
|
9
9
|
orderItems: 'plg_shop_order_items',
|
|
10
10
|
customers: 'plg_shop_customers',
|
|
11
11
|
discounts: 'plg_shop_discounts',
|
|
12
|
+
salesChannels: 'plg_shop_channels',
|
|
12
13
|
// Core, not plg_shop_*: a delivery zone is a fact about the business, and
|
|
13
14
|
// fulfilment and financial read it too. Same reasoning as addresses and
|
|
14
15
|
// orders — see migration 0021.
|
package/src/types.ts
CHANGED
|
@@ -453,6 +453,9 @@ export type DiscountType = 'percentage' | 'fixed_amount' | 'free_shipping' | 'bu
|
|
|
453
453
|
export type DiscountMethod = 'code' | 'automatic'
|
|
454
454
|
export type DiscountStatus = 'active' | 'expired' | 'scheduled' | 'draft'
|
|
455
455
|
|
|
456
|
+
/** What subset of the cart a discount applies to. 'all' = order-wide (default). */
|
|
457
|
+
export type DiscountScope = 'all' | 'products' | 'categories'
|
|
458
|
+
|
|
456
459
|
export interface Discount {
|
|
457
460
|
id: string
|
|
458
461
|
tenantId: string
|
|
@@ -463,6 +466,15 @@ export interface Discount {
|
|
|
463
466
|
value: number
|
|
464
467
|
usageLimit: number | null
|
|
465
468
|
oncePerCustomer: boolean
|
|
469
|
+
/** Minimum order subtotal for the coupon to apply (pedido mínimo). Null = none. */
|
|
470
|
+
minSubtotal: number | null
|
|
471
|
+
/** Scope of the discount. 'products'/'categories' restrict it to the eligible
|
|
472
|
+
* line items; the %/valor then applies to that eligible subtotal only. */
|
|
473
|
+
appliesTo: DiscountScope
|
|
474
|
+
/** When appliesTo='products', the eligible product ids. */
|
|
475
|
+
productIds: string[]
|
|
476
|
+
/** When appliesTo='categories', the eligible category ids. */
|
|
477
|
+
categoryIds: string[]
|
|
466
478
|
startsAt: string
|
|
467
479
|
endsAt: string | null
|
|
468
480
|
status: DiscountStatus
|
|
@@ -479,6 +491,10 @@ export interface CreateDiscountInput {
|
|
|
479
491
|
value: number
|
|
480
492
|
usageLimit?: number
|
|
481
493
|
oncePerCustomer?: boolean
|
|
494
|
+
minSubtotal?: number | null
|
|
495
|
+
appliesTo?: DiscountScope
|
|
496
|
+
productIds?: string[]
|
|
497
|
+
categoryIds?: string[]
|
|
482
498
|
startsAt?: string
|
|
483
499
|
endsAt?: string
|
|
484
500
|
status?: DiscountStatus
|
|
@@ -492,6 +508,10 @@ export interface UpdateDiscountInput {
|
|
|
492
508
|
value?: number
|
|
493
509
|
usageLimit?: number | null
|
|
494
510
|
oncePerCustomer?: boolean
|
|
511
|
+
minSubtotal?: number | null
|
|
512
|
+
appliesTo?: DiscountScope
|
|
513
|
+
productIds?: string[]
|
|
514
|
+
categoryIds?: string[]
|
|
495
515
|
startsAt?: string
|
|
496
516
|
endsAt?: string | null
|
|
497
517
|
status?: DiscountStatus
|
|
@@ -505,6 +525,53 @@ export interface ListDiscountsOptions {
|
|
|
505
525
|
offset?: number
|
|
506
526
|
}
|
|
507
527
|
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
// Sales channels — the store's points of sale (Loja Online, WhatsApp, TikTok…).
|
|
530
|
+
// A channel is per-tenant (the store IS the tenant). The 'online_store' channel
|
|
531
|
+
// points at the merchant's fayz storefront; the rest are external links the
|
|
532
|
+
// merchant maintains. Surfaced as a "Canais de venda" group in the admin nav.
|
|
533
|
+
// ---------------------------------------------------------------------------
|
|
534
|
+
|
|
535
|
+
export type SalesChannelKind =
|
|
536
|
+
| 'online_store'
|
|
537
|
+
| 'whatsapp'
|
|
538
|
+
| 'instagram'
|
|
539
|
+
| 'tiktok_shop'
|
|
540
|
+
| 'marketplace'
|
|
541
|
+
| 'physical'
|
|
542
|
+
| 'custom'
|
|
543
|
+
|
|
544
|
+
export interface SalesChannel {
|
|
545
|
+
id: string
|
|
546
|
+
tenantId: string
|
|
547
|
+
kind: SalesChannelKind
|
|
548
|
+
label: string
|
|
549
|
+
/** External URL opened from the sidebar / channels page. Null while unconfigured
|
|
550
|
+
* (e.g. the Loja Online channel before the storefront address is set). */
|
|
551
|
+
url: string | null
|
|
552
|
+
enabled: boolean
|
|
553
|
+
/** Sidebar/list ordering (lower first). */
|
|
554
|
+
position: number
|
|
555
|
+
createdAt: string
|
|
556
|
+
updatedAt: string
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export interface CreateSalesChannelInput {
|
|
560
|
+
kind: SalesChannelKind
|
|
561
|
+
label: string
|
|
562
|
+
url?: string | null
|
|
563
|
+
enabled?: boolean
|
|
564
|
+
position?: number
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export interface UpdateSalesChannelInput {
|
|
568
|
+
kind?: SalesChannelKind
|
|
569
|
+
label?: string
|
|
570
|
+
url?: string | null
|
|
571
|
+
enabled?: boolean
|
|
572
|
+
position?: number
|
|
573
|
+
}
|
|
574
|
+
|
|
508
575
|
// ---------------------------------------------------------------------------
|
|
509
576
|
// Commerce primitives (FAY-1193) — provider-agnostic helpers for custom workflows
|
|
510
577
|
// ---------------------------------------------------------------------------
|
package/dist/catalog.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mock-catalog.ts"],"names":[],"mappings":"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","file":"catalog.cjs","sourcesContent":["import type { Product, ProductImage, Category, Discount } from './types'\n\n// ---------------------------------------------------------------------------\n// Deterministic, offline product catalog for the mock provider.\n// Product names sourced from the Fayz client repo references\n// (apps/web ShopSection mockups + original shop seeds), expanded to a\n// 16-product / 4-category Shopify-style catalog.\n// ---------------------------------------------------------------------------\n\nconst T0 = '2026-01-01T12:00:00.000Z'\n\n/** 600x600 gradient SVG with product initials — no network, e2e-stable. */\nexport function svgPlaceholder(label: string, hueA: number, hueB: number): string {\n const initials = label\n .split(/\\s+/)\n .map((w) => w[0])\n .slice(0, 2)\n .join('')\n .toUpperCase()\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"600\" height=\"600\">` +\n `<defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">` +\n `<stop offset=\"0\" stop-color=\"hsl(${hueA},45%,82%)\"/>` +\n `<stop offset=\"1\" stop-color=\"hsl(${hueB},50%,62%)\"/>` +\n `</linearGradient></defs>` +\n `<rect width=\"600\" height=\"600\" fill=\"url(#g)\"/>` +\n `<text x=\"300\" y=\"330\" font-family=\"sans-serif\" font-size=\"120\" font-weight=\"700\" ` +\n `fill=\"rgba(255,255,255,0.85)\" text-anchor=\"middle\">${initials}</text>` +\n `</svg>`\n return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`\n}\n\nfunction image(productId: string, name: string, hueA: number, hueB: number, idx = 0): ProductImage {\n return {\n id: `img-${productId}-${idx}`,\n productId,\n url: svgPlaceholder(name, hueA, hueB),\n altText: name,\n sortOrder: idx,\n isPrimary: idx === 0,\n createdAt: T0,\n }\n}\n\nexport const MOCK_CATEGORIES: Category[] = [\n { id: 'cat-apparel', tenantId: 'mock', name: 'Apparel', slug: 'apparel', description: 'Clothing and wearables', parentId: null, sortOrder: 0, createdAt: T0 },\n { id: 'cat-bags', tenantId: 'mock', name: 'Bags & Accessories', slug: 'bags-accessories', description: 'Bags, wallets and carry gear', parentId: null, sortOrder: 1, createdAt: T0 },\n { id: 'cat-home', tenantId: 'mock', name: 'Home & Kitchen', slug: 'home-kitchen', description: 'Everyday home goods', parentId: null, sortOrder: 2, createdAt: T0 },\n { id: 'cat-tech', tenantId: 'mock', name: 'Stationery & Tech', slug: 'stationery-tech', description: 'Desk, audio and paper goods', parentId: null, sortOrder: 3, createdAt: T0 },\n]\n\ninterface SeedProduct {\n id: string\n name: string\n slug: string\n description: string\n price: number\n compareAtPrice?: number\n inventoryCount: number\n sku: string\n categoryId: string\n hues: [number, number]\n weight: number\n}\n\nconst SEEDS: SeedProduct[] = [\n // --- Apparel ---\n { id: 'p-01', name: 'Classic T-Shirt', slug: 'classic-t-shirt', description: 'A timeless crew-neck tee in heavyweight combed cotton. Pre-shrunk, breathable, and built to outlast trends.', price: 59.9, compareAtPrice: 79.9, inventoryCount: 50, sku: 'TSH-001', categoryId: 'cat-apparel', hues: [210, 240], weight: 0.2 },\n { id: 'p-02', name: 'Linen Shirt', slug: 'linen-shirt', description: 'A relaxed-fit shirt in 100% European linen. Naturally breathable and softer with every wash.', price: 189.9, inventoryCount: 24, sku: 'LNS-002', categoryId: 'cat-apparel', hues: [35, 20], weight: 0.3 },\n { id: 'p-03', name: 'Denim Jacket', slug: 'denim-jacket', description: 'A structured trucker jacket in 12oz raw denim. Ages beautifully with hidden interior pockets.', price: 299.9, inventoryCount: 12, sku: 'DNJ-003', categoryId: 'cat-apparel', hues: [215, 200], weight: 0.8 },\n { id: 'p-04', name: 'Wool Beanie', slug: 'wool-beanie', description: 'A double-layer merino wool beanie. Warm without itch, with a low-profile cuffed fit.', price: 79.9, inventoryCount: 0, sku: 'WBN-004', categoryId: 'cat-apparel', hues: [0, 350], weight: 0.1 },\n\n // --- Bags & Accessories ---\n { id: 'p-05', name: 'Canvas Tote Bag', slug: 'canvas-tote-bag', description: 'An everyday carryall in 16oz waxed canvas with reinforced straps and an interior zip pocket.', price: 39.9, inventoryCount: 30, sku: 'BAG-001', categoryId: 'cat-bags', hues: [80, 110], weight: 0.3 },\n { id: 'p-06', name: 'Leather Backpack', slug: 'leather-backpack', description: 'Full-grain leather backpack with a padded 15\" laptop sleeve, magnetic closures and brass hardware.', price: 549.9, inventoryCount: 8, sku: 'LBP-006', categoryId: 'cat-bags', hues: [25, 15], weight: 1.4 },\n { id: 'p-07', name: 'Leather Card Wallet', slug: 'leather-card-wallet', description: 'A slim six-card wallet in vegetable-tanned leather. RFID-shielded with a center cash slot.', price: 89.9, compareAtPrice: 119.9, inventoryCount: 45, sku: 'LCW-007', categoryId: 'cat-bags', hues: [30, 45], weight: 0.08 },\n { id: 'p-08', name: 'Travel Duffel', slug: 'travel-duffel', description: 'A 40L weekender in ballistic nylon with a trolley sleeve, shoe compartment and detachable strap.', price: 329.9, inventoryCount: 15, sku: 'TDF-008', categoryId: 'cat-bags', hues: [150, 170], weight: 1.1 },\n\n // --- Home & Kitchen ---\n { id: 'p-09', name: 'Ceramic Coffee Mug', slug: 'ceramic-coffee-mug', description: 'A 350ml stoneware mug with a matte glaze and comfortable wide handle. Dishwasher and microwave safe.', price: 34.9, inventoryCount: 0, sku: 'MUG-001', categoryId: 'cat-home', hues: [190, 210], weight: 0.4 },\n { id: 'p-10', name: 'Scented Soy Candle', slug: 'scented-soy-candle', description: 'Hand-poured soy wax candle with cedar and bergamot notes. 45-hour burn time in a reusable glass jar.', price: 69.9, inventoryCount: 60, sku: 'SSC-010', categoryId: 'cat-home', hues: [45, 30], weight: 0.5 },\n { id: 'p-11', name: 'Bamboo Cutting Board', slug: 'bamboo-cutting-board', description: 'An end-grain bamboo board that is gentle on knives, with a juice groove and inset handles.', price: 119.9, inventoryCount: 22, sku: 'BCB-011', categoryId: 'cat-home', hues: [90, 70], weight: 1.2 },\n { id: 'p-12', name: 'Stoneware Plate Set', slug: 'stoneware-plate-set', description: 'A set of four reactive-glaze dinner plates. Each piece is unique, oven-safe to 220°C.', price: 249.9, inventoryCount: 10, sku: 'SPS-012', categoryId: 'cat-home', hues: [200, 180], weight: 2.4 },\n\n // --- Stationery & Tech ---\n { id: 'p-13', name: 'Wireless Headphones', slug: 'wireless-headphones', description: 'Over-ear headphones with active noise cancelling, 40-hour battery and multipoint Bluetooth 5.3.', price: 899.9, inventoryCount: 18, sku: 'WHP-013', categoryId: 'cat-tech', hues: [260, 280], weight: 0.35 },\n { id: 'p-14', name: 'Notebook Set', slug: 'notebook-set', description: 'Three A5 dot-grid notebooks with 120gsm paper, lay-flat binding and numbered pages.', price: 29.9, inventoryCount: 80, sku: 'NBS-014', categoryId: 'cat-tech', hues: [330, 310], weight: 0.45 },\n { id: 'p-15', name: 'Bluetooth Speaker', slug: 'bluetooth-speaker', description: 'A pocket-size speaker with surprising low end, IPX7 waterproofing and 14-hour playtime.', price: 199.9, compareAtPrice: 259.9, inventoryCount: 26, sku: 'BTS-015', categoryId: 'cat-tech', hues: [170, 190], weight: 0.3 },\n { id: 'p-16', name: 'Felt Desk Mat', slug: 'felt-desk-mat', description: 'A 80x30cm merino felt desk mat with a cork base. Protects the desk and quiets the keyboard.', price: 99.9, inventoryCount: 34, sku: 'FDM-016', categoryId: 'cat-tech', hues: [220, 230], weight: 0.6 },\n]\n\nconst CATEGORY_NAME: Record<string, string> = Object.fromEntries(\n MOCK_CATEGORIES.map((c) => [c.id, c.name]),\n)\n\nexport const MOCK_PRODUCTS: Product[] = SEEDS.map((s, i) => ({\n id: s.id,\n tenantId: 'mock',\n name: s.name,\n slug: s.slug,\n description: s.description,\n price: s.price,\n compareAtPrice: s.compareAtPrice ?? null,\n currency: 'BRL',\n status: 'active',\n inventoryCount: s.inventoryCount,\n sku: s.sku,\n sortOrder: i,\n metadata: {},\n images: [image(s.id, s.name, s.hues[0], s.hues[1])],\n categoryId: s.categoryId,\n categoryName: CATEGORY_NAME[s.categoryId] ?? null,\n isPhysical: true,\n weight: s.weight,\n weightUnit: 'kg',\n // staggered timestamps → deterministic 'newest' sort (p-16 newest)\n createdAt: `2026-01-${String(i + 1).padStart(2, '0')}T12:00:00.000Z`,\n updatedAt: `2026-01-${String(i + 1).padStart(2, '0')}T12:00:00.000Z`,\n}))\n\nexport const MOCK_DISCOUNTS: Discount[] = [\n {\n id: 'disc-welcome',\n tenantId: 'mock',\n title: 'Welcome 10%',\n code: 'BEMVINDO10',\n type: 'percentage',\n method: 'code',\n value: 10,\n usageLimit: null,\n oncePerCustomer: false,\n startsAt: T0,\n endsAt: null,\n status: 'active',\n timesUsed: 0,\n createdAt: T0,\n updatedAt: T0,\n },\n]\n\n// ---------------------------------------------------------------------------\n// buildMockCatalog — terse per-store catalog definition. Generates ids,\n// slugs, SVG imagery and staggered timestamps so every store gets a\n// deterministic, e2e-stable catalog from a few lines of data.\n// ---------------------------------------------------------------------------\n\nexport interface CatalogCategoryInput {\n name: string\n description?: string\n image?: string\n}\n\nexport interface CatalogProductInput {\n name: string\n description: string\n price: number\n compareAtPrice?: number\n inventory: number\n sku: string\n /** Category name — must match one of the categories */\n category: string\n hues?: [number, number]\n weight?: number\n image?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface CatalogInput {\n categories: CatalogCategoryInput[]\n products: CatalogProductInput[]\n /** Percentage discount codes, e.g. [{ code: 'BEMVINDO10', percent: 10 }] */\n discounts?: Array<{ code: string; percent: number; title?: string }>\n currency?: string\n}\n\nfunction slugifyName(name: string): string {\n return name\n .toLowerCase()\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n}\n\nexport function buildMockCatalog(input: CatalogInput): { products: Product[]; categories: Category[]; discounts: Discount[] } {\n const categories: Category[] = input.categories.map((c, i) => ({\n id: `cat-${slugifyName(c.name)}`,\n tenantId: 'mock',\n name: c.name,\n slug: slugifyName(c.name),\n description: c.description ?? null,\n parentId: null,\n sortOrder: i,\n imageUrl: c.image ?? null,\n createdAt: T0,\n }))\n const byName = Object.fromEntries(categories.map((c) => [c.name, c]))\n\n const products: Product[] = input.products.map((p, i) => {\n const cat = byName[p.category]\n const id = `p-${String(i + 1).padStart(2, '0')}`\n const hues = p.hues ?? [(i * 47) % 360, (i * 47 + 50) % 360]\n const stamp = `2026-01-${String((i % 27) + 1).padStart(2, '0')}T12:00:00.000Z`\n return {\n id,\n tenantId: 'mock',\n name: p.name,\n slug: slugifyName(p.name),\n description: p.description,\n price: p.price,\n compareAtPrice: p.compareAtPrice ?? null,\n currency: input.currency ?? 'BRL',\n status: 'active',\n inventoryCount: p.inventory,\n sku: p.sku,\n sortOrder: i,\n metadata: p.metadata ?? {},\n images: [\n {\n id: `img-${id}-0`,\n productId: id,\n url: p.image ?? svgPlaceholder(p.name, hues[0], hues[1]),\n altText: p.name,\n sortOrder: 0,\n isPrimary: true,\n createdAt: T0,\n },\n ],\n categoryId: cat?.id ?? null,\n categoryName: cat?.name ?? null,\n isPhysical: true,\n weight: p.weight ?? 0.5,\n weightUnit: 'kg',\n createdAt: stamp,\n updatedAt: stamp,\n }\n })\n\n const discounts: Discount[] = (input.discounts ?? []).map((d, i) => ({\n id: `disc-${slugifyName(d.code)}`,\n tenantId: 'mock',\n title: d.title ?? `${d.percent}% off`,\n code: d.code,\n type: 'percentage',\n method: 'code',\n value: d.percent,\n usageLimit: null,\n oncePerCustomer: false,\n startsAt: T0,\n endsAt: null,\n status: 'active',\n timesUsed: 0,\n createdAt: T0,\n updatedAt: T0,\n }))\n\n return { products, categories, discounts }\n}\n"]}
|