@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.
Files changed (47) hide show
  1. package/dist/catalog.js +2 -168
  2. package/dist/catalog.js.map +1 -1
  3. package/dist/{catalog.cjs → chunk-CTKZ6O2J.js} +11 -9
  4. package/dist/chunk-CTKZ6O2J.js.map +1 -0
  5. package/dist/chunk-IYQHKMLB.js +21 -0
  6. package/dist/chunk-IYQHKMLB.js.map +1 -0
  7. package/dist/chunk-JR25TISO.js +3 -0
  8. package/dist/chunk-JR25TISO.js.map +1 -0
  9. package/dist/{mock.cjs → chunk-SVTCJ6HP.js} +62 -103
  10. package/dist/chunk-SVTCJ6HP.js.map +1 -0
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +73 -579
  14. package/dist/index.js.map +1 -1
  15. package/dist/mock-catalog.d.ts.map +1 -1
  16. package/dist/mock-provider.d.ts +8 -1
  17. package/dist/mock-provider.d.ts.map +1 -1
  18. package/dist/mock.js +2 -476
  19. package/dist/mock.js.map +1 -1
  20. package/dist/provider.d.ts +5 -1
  21. package/dist/provider.d.ts.map +1 -1
  22. package/dist/runtime.js +3 -492
  23. package/dist/runtime.js.map +1 -1
  24. package/dist/supabase-provider.d.ts +5 -1
  25. package/dist/supabase-provider.d.ts.map +1 -1
  26. package/dist/tables.d.ts +1 -0
  27. package/dist/tables.d.ts.map +1 -1
  28. package/dist/types.d.ts +48 -0
  29. package/dist/types.d.ts.map +1 -1
  30. package/package.json +22 -26
  31. package/src/index.ts +1 -0
  32. package/src/mock-catalog.ts +8 -0
  33. package/src/mock-provider.ts +63 -5
  34. package/src/provider.ts +8 -0
  35. package/src/supabase-provider.ts +64 -1
  36. package/src/tables.ts +1 -0
  37. package/src/types.ts +67 -0
  38. package/dist/catalog.cjs.map +0 -1
  39. package/dist/index.cjs +0 -1250
  40. package/dist/index.cjs.map +0 -1
  41. package/dist/mock.cjs.map +0 -1
  42. package/dist/provider.cjs +0 -4
  43. package/dist/provider.cjs.map +0 -1
  44. package/dist/runtime.cjs +0 -498
  45. package/dist/runtime.cjs.map +0 -1
  46. package/dist/types.cjs +0 -4
  47. package/dist/types.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,583 +1,23 @@
1
+ import { getShopProvider } from './chunk-IYQHKMLB.js';
2
+ export { getShopProvider, resetShopProvider, setShopProvider } from './chunk-IYQHKMLB.js';
3
+ import './chunk-JR25TISO.js';
4
+ export { MockShopProvider, createMockShopProvider } from './chunk-SVTCJ6HP.js';
5
+ export { MOCK_CATEGORIES, MOCK_DISCOUNTS, MOCK_PRODUCTS, buildMockCatalog, svgPlaceholder } from './chunk-CTKZ6O2J.js';
1
6
  import { getSupabaseClientOptional } from '@fayz-ai/core';
2
7
 
3
- // src/mock-catalog.ts
4
- var T0 = "2026-01-01T12:00:00.000Z";
5
- function svgPlaceholder(label, hueA, hueB) {
6
- const initials = label.split(/\s+/).map((w) => w[0]).slice(0, 2).join("").toUpperCase();
7
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="hsl(${hueA},45%,82%)"/><stop offset="1" stop-color="hsl(${hueB},50%,62%)"/></linearGradient></defs><rect width="600" height="600" fill="url(#g)"/><text x="300" y="330" font-family="sans-serif" font-size="120" font-weight="700" fill="rgba(255,255,255,0.85)" text-anchor="middle">${initials}</text></svg>`;
8
- return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
9
- }
10
- function image(productId, name, hueA, hueB, idx = 0) {
11
- return {
12
- id: `img-${productId}-${idx}`,
13
- productId,
14
- url: svgPlaceholder(name, hueA, hueB),
15
- altText: name,
16
- sortOrder: idx,
17
- isPrimary: idx === 0,
18
- createdAt: T0
19
- };
20
- }
21
- var MOCK_CATEGORIES = [
22
- { id: "cat-apparel", tenantId: "mock", name: "Apparel", slug: "apparel", description: "Clothing and wearables", parentId: null, sortOrder: 0, createdAt: T0 },
23
- { id: "cat-bags", tenantId: "mock", name: "Bags & Accessories", slug: "bags-accessories", description: "Bags, wallets and carry gear", parentId: null, sortOrder: 1, createdAt: T0 },
24
- { id: "cat-home", tenantId: "mock", name: "Home & Kitchen", slug: "home-kitchen", description: "Everyday home goods", parentId: null, sortOrder: 2, createdAt: T0 },
25
- { id: "cat-tech", tenantId: "mock", name: "Stationery & Tech", slug: "stationery-tech", description: "Desk, audio and paper goods", parentId: null, sortOrder: 3, createdAt: T0 }
26
- ];
27
- var SEEDS = [
28
- // --- Apparel ---
29
- { 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 },
30
- { 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 },
31
- { 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 },
32
- { 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 },
33
- // --- Bags & Accessories ---
34
- { 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 },
35
- { 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 },
36
- { 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 },
37
- { 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 },
38
- // --- Home & Kitchen ---
39
- { 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 },
40
- { 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 },
41
- { 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 },
42
- { 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\xB0C.", price: 249.9, inventoryCount: 10, sku: "SPS-012", categoryId: "cat-home", hues: [200, 180], weight: 2.4 },
43
- // --- Stationery & Tech ---
44
- { 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 },
45
- { 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 },
46
- { 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 },
47
- { 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 }
48
- ];
49
- var CATEGORY_NAME = Object.fromEntries(
50
- MOCK_CATEGORIES.map((c) => [c.id, c.name])
51
- );
52
- var MOCK_PRODUCTS = SEEDS.map((s, i) => ({
53
- id: s.id,
54
- tenantId: "mock",
55
- name: s.name,
56
- slug: s.slug,
57
- description: s.description,
58
- price: s.price,
59
- compareAtPrice: s.compareAtPrice ?? null,
60
- currency: "BRL",
61
- status: "active",
62
- inventoryCount: s.inventoryCount,
63
- sku: s.sku,
64
- sortOrder: i,
65
- metadata: {},
66
- images: [image(s.id, s.name, s.hues[0], s.hues[1])],
67
- categoryId: s.categoryId,
68
- categoryName: CATEGORY_NAME[s.categoryId] ?? null,
69
- isPhysical: true,
70
- weight: s.weight,
71
- weightUnit: "kg",
72
- // staggered timestamps → deterministic 'newest' sort (p-16 newest)
73
- createdAt: `2026-01-${String(i + 1).padStart(2, "0")}T12:00:00.000Z`,
74
- updatedAt: `2026-01-${String(i + 1).padStart(2, "0")}T12:00:00.000Z`
75
- }));
76
- var MOCK_DISCOUNTS = [
77
- {
78
- id: "disc-welcome",
79
- tenantId: "mock",
80
- title: "Welcome 10%",
81
- code: "BEMVINDO10",
82
- type: "percentage",
83
- method: "code",
84
- value: 10,
85
- usageLimit: null,
86
- oncePerCustomer: false,
87
- startsAt: T0,
88
- endsAt: null,
89
- status: "active",
90
- timesUsed: 0,
91
- createdAt: T0,
92
- updatedAt: T0
93
- }
94
- ];
95
- function slugifyName(name) {
96
- return name.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
97
- }
98
- function buildMockCatalog(input) {
99
- const categories = input.categories.map((c, i) => ({
100
- id: `cat-${slugifyName(c.name)}`,
101
- tenantId: "mock",
102
- name: c.name,
103
- slug: slugifyName(c.name),
104
- description: c.description ?? null,
105
- parentId: null,
106
- sortOrder: i,
107
- imageUrl: c.image ?? null,
108
- createdAt: T0
109
- }));
110
- const byName = Object.fromEntries(categories.map((c) => [c.name, c]));
111
- const products = input.products.map((p, i) => {
112
- const cat = byName[p.category];
113
- const id = `p-${String(i + 1).padStart(2, "0")}`;
114
- const hues = p.hues ?? [i * 47 % 360, (i * 47 + 50) % 360];
115
- const stamp = `2026-01-${String(i % 27 + 1).padStart(2, "0")}T12:00:00.000Z`;
116
- return {
117
- id,
118
- tenantId: "mock",
119
- name: p.name,
120
- slug: slugifyName(p.name),
121
- description: p.description,
122
- price: p.price,
123
- compareAtPrice: p.compareAtPrice ?? null,
124
- currency: input.currency ?? "BRL",
125
- status: "active",
126
- inventoryCount: p.inventory,
127
- sku: p.sku,
128
- sortOrder: i,
129
- metadata: p.metadata ?? {},
130
- images: [
131
- {
132
- id: `img-${id}-0`,
133
- productId: id,
134
- url: p.image ?? svgPlaceholder(p.name, hues[0], hues[1]),
135
- altText: p.name,
136
- sortOrder: 0,
137
- isPrimary: true,
138
- createdAt: T0
139
- }
140
- ],
141
- categoryId: cat?.id ?? null,
142
- categoryName: cat?.name ?? null,
143
- isPhysical: true,
144
- weight: p.weight ?? 0.5,
145
- weightUnit: "kg",
146
- createdAt: stamp,
147
- updatedAt: stamp
148
- };
149
- });
150
- const discounts = (input.discounts ?? []).map((d, i) => ({
151
- id: `disc-${slugifyName(d.code)}`,
152
- tenantId: "mock",
153
- title: d.title ?? `${d.percent}% off`,
154
- code: d.code,
155
- type: "percentage",
156
- method: "code",
157
- value: d.percent,
158
- usageLimit: null,
159
- oncePerCustomer: false,
160
- startsAt: T0,
161
- endsAt: null,
162
- status: "active",
163
- timesUsed: 0,
164
- createdAt: T0,
165
- updatedAt: T0
166
- }));
167
- return { products, categories, discounts };
168
- }
169
-
170
- // src/mock-provider.ts
171
- function uid() {
172
- return Math.random().toString(36).slice(2);
173
- }
174
- function now() {
175
- return (/* @__PURE__ */ new Date()).toISOString();
176
- }
177
- var ORDERS_KEY = "fayz.shop.mock.orders.v1";
178
- var CUSTOMERS_KEY = "fayz.shop.mock.customers.v1";
179
- var ENQUIRIES_KEY = "fayz.shop.mock.enquiries.v1";
180
- function loadStored(key) {
181
- try {
182
- if (typeof localStorage === "undefined") return [];
183
- const raw = localStorage.getItem(key);
184
- return raw ? JSON.parse(raw) : [];
185
- } catch {
186
- return [];
187
- }
188
- }
189
- function store(key, value) {
190
- try {
191
- if (typeof localStorage === "undefined") return;
192
- localStorage.setItem(key, JSON.stringify(value));
193
- } catch {
194
- }
195
- }
196
- var MockShopProvider = class {
197
- constructor(seed) {
198
- this.orders = loadStored(ORDERS_KEY);
199
- this.customers = loadStored(CUSTOMERS_KEY);
200
- this.enquiries = loadStored(ENQUIRIES_KEY);
201
- this.products = (seed?.products ?? MOCK_PRODUCTS).map((p) => ({ ...p }));
202
- this.categories = (seed?.categories ?? MOCK_CATEGORIES).map((c) => ({ ...c }));
203
- this.discounts = (seed?.discounts ?? MOCK_DISCOUNTS).map((d) => ({ ...d }));
204
- }
205
- // Re-read before mutating: HMR can re-instantiate the provider mid-session
206
- // and a stale in-memory copy must not clobber persisted records.
207
- syncOrders() {
208
- this.orders = loadStored(ORDERS_KEY);
209
- }
210
- syncCustomers() {
211
- this.customers = loadStored(CUSTOMERS_KEY);
212
- }
213
- syncEnquiries() {
214
- this.enquiries = loadStored(ENQUIRIES_KEY);
215
- }
216
- persistOrders() {
217
- store(ORDERS_KEY, this.orders);
218
- }
219
- persistCustomers() {
220
- store(CUSTOMERS_KEY, this.customers);
221
- }
222
- persistEnquiries() {
223
- store(ENQUIRIES_KEY, this.enquiries);
224
- }
225
- // ---------------------------------------------------------------------------
226
- // Products
227
- // ---------------------------------------------------------------------------
228
- async listProducts(opts) {
229
- let list = [...this.products];
230
- if (opts?.status) list = list.filter((p) => p.status === opts.status);
231
- if (opts?.categoryId) list = list.filter((p) => p.categoryId === opts.categoryId);
232
- if (opts?.slug) list = list.filter((p) => p.slug === opts.slug);
233
- if (opts?.search) {
234
- const q = opts.search.toLowerCase();
235
- list = list.filter(
236
- (p) => p.name.toLowerCase().includes(q) || (p.description ?? "").toLowerCase().includes(q) || (p.sku ?? "").toLowerCase().includes(q)
237
- );
238
- }
239
- const orderBy = opts?.orderBy ?? "sort_order";
240
- const dir = opts?.order === "desc" ? -1 : 1;
241
- list.sort((a, b) => {
242
- switch (orderBy) {
243
- case "name":
244
- return a.name.localeCompare(b.name) * dir;
245
- case "price":
246
- return (a.price - b.price) * dir;
247
- case "created_at":
248
- return a.createdAt.localeCompare(b.createdAt) * dir;
249
- default:
250
- return (a.sortOrder - b.sortOrder) * dir;
251
- }
252
- });
253
- const offset = opts?.offset ?? 0;
254
- if (offset || opts?.limit) list = list.slice(offset, opts?.limit ? offset + opts.limit : void 0);
255
- return list;
256
- }
257
- async getProduct(id) {
258
- return this.products.find((p) => p.id === id) ?? null;
259
- }
260
- async createProduct(input) {
261
- const p = { id: uid(), tenantId: "mock", name: input.name, slug: input.slug ?? input.name.toLowerCase().replace(/\s+/g, "-"), description: input.description ?? null, price: input.price, compareAtPrice: input.compareAtPrice ?? null, currency: input.currency ?? "BRL", status: input.status ?? "active", inventoryCount: input.inventoryCount ?? 0, sku: input.sku ?? null, sortOrder: input.sortOrder ?? 0, metadata: input.metadata ?? {}, images: [], categoryId: input.categoryId ?? null, categoryName: null, isPhysical: input.isPhysical ?? true, weight: input.weight ?? null, weightUnit: input.weightUnit ?? "kg", createdAt: now(), updatedAt: now() };
262
- this.products.push(p);
263
- return p;
264
- }
265
- async updateProduct(id, input) {
266
- const i = this.products.findIndex((p) => p.id === id);
267
- if (i < 0) throw new Error("Product not found");
268
- this.products[i] = { ...this.products[i], ...input, updatedAt: now() };
269
- return this.products[i];
270
- }
271
- async deleteProduct(id) {
272
- this.products = this.products.filter((p) => p.id !== id);
273
- }
274
- async uploadProductImage(_productId, _file) {
275
- throw new Error("Image upload not available in mock mode");
276
- }
277
- async deleteProductImage(_imageId) {
278
- }
279
- async createProductEnquiry(input) {
280
- this.syncEnquiries();
281
- const ts = now();
282
- const enquiry = {
283
- id: uid(),
284
- tenantId: "mock",
285
- productId: input.productId ?? null,
286
- productName: input.productName,
287
- productSlug: input.productSlug ?? null,
288
- customerName: input.customerName,
289
- customerEmail: input.customerEmail,
290
- customerPhone: input.customerPhone ?? null,
291
- subject: input.subject ?? null,
292
- message: input.message,
293
- sourceUrl: input.sourceUrl ?? null,
294
- status: "new",
295
- metadata: input.metadata ?? {},
296
- createdAt: ts,
297
- updatedAt: ts
298
- };
299
- this.enquiries.unshift(enquiry);
300
- this.persistEnquiries();
301
- return enquiry;
302
- }
303
- async listProductEnquiries(opts) {
304
- this.syncEnquiries();
305
- let list = [...this.enquiries];
306
- if (opts?.productId) list = list.filter((e) => e.productId === opts.productId);
307
- if (opts?.customerEmail) {
308
- const email = opts.customerEmail.toLowerCase();
309
- list = list.filter((e) => e.customerEmail.toLowerCase() === email);
310
- }
311
- if (opts?.status) list = list.filter((e) => e.status === opts.status);
312
- list.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
313
- const offset = opts?.offset ?? 0;
314
- if (offset || opts?.limit) list = list.slice(offset, opts?.limit ? offset + opts.limit : void 0);
315
- return list;
316
- }
317
- // ---------------------------------------------------------------------------
318
- // Categories
319
- // ---------------------------------------------------------------------------
320
- async listCategories() {
321
- return [...this.categories].sort((a, b) => a.sortOrder - b.sortOrder);
322
- }
323
- async createCategory(input) {
324
- const c = { id: uid(), tenantId: "mock", name: input.name, slug: input.slug ?? input.name.toLowerCase().replace(/\s+/g, "-"), description: input.description ?? null, parentId: input.parentId ?? null, sortOrder: input.sortOrder ?? 0, createdAt: now() };
325
- this.categories.push(c);
326
- return c;
327
- }
328
- async updateCategory(id, input) {
329
- const i = this.categories.findIndex((c) => c.id === id);
330
- if (i < 0) throw new Error("Category not found");
331
- this.categories[i] = { ...this.categories[i], ...input };
332
- return this.categories[i];
333
- }
334
- async deleteCategory(id) {
335
- this.categories = this.categories.filter((c) => c.id !== id);
336
- }
337
- // ---------------------------------------------------------------------------
338
- // Orders
339
- // ---------------------------------------------------------------------------
340
- async listOrders(opts) {
341
- this.syncOrders();
342
- let list = [...this.orders];
343
- if (opts?.status) list = list.filter((o) => o.status === opts.status);
344
- if (opts?.financialStatus) list = list.filter((o) => o.financialStatus === opts.financialStatus);
345
- if (opts?.fulfillmentStatus) list = list.filter((o) => o.fulfillmentStatus === opts.fulfillmentStatus);
346
- if (opts?.customerId) list = list.filter((o) => o.customerId === opts.customerId);
347
- if (opts?.customerEmail) {
348
- const email = opts.customerEmail.toLowerCase();
349
- list = list.filter((o) => (o.customerEmail ?? "").toLowerCase() === email);
350
- }
351
- if (opts?.search) {
352
- const q = opts.search.toLowerCase();
353
- list = list.filter(
354
- (o) => (o.customerName ?? "").toLowerCase().includes(q) || (o.customerEmail ?? "").toLowerCase().includes(q) || String(o.orderNumber).includes(q)
355
- );
356
- }
357
- list.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
358
- if (opts?.limit) list = list.slice(0, opts.limit);
359
- return list;
360
- }
361
- async getOrder(id) {
362
- this.syncOrders();
363
- return this.orders.find((o) => o.id === id) ?? null;
364
- }
365
- async createOrder(input) {
366
- this.syncOrders();
367
- const subtotal = input.items.reduce((s, i) => s + i.quantity * i.unitPrice, 0);
368
- const discountTotal = input.discountTotal ?? 0;
369
- const shippingTotal = input.shippingTotal ?? 0;
370
- const total = Math.round((subtotal - discountTotal + shippingTotal) * 100) / 100;
371
- const orderId = uid();
372
- const maxNumber = this.orders.reduce((m, o2) => Math.max(m, o2.orderNumber), 1e3);
373
- const o = {
374
- id: orderId,
375
- tenantId: "mock",
376
- orderNumber: maxNumber + 1,
377
- status: "open",
378
- financialStatus: "pending",
379
- fulfillmentStatus: "unfulfilled",
380
- currency: input.currency ?? "BRL",
381
- subtotal: Math.round(subtotal * 100) / 100,
382
- taxTotal: 0,
383
- discountTotal,
384
- shippingTotal,
385
- total,
386
- customerId: input.customerId ?? null,
387
- customerName: input.customerName ?? null,
388
- customerEmail: input.customerEmail ?? null,
389
- discountCode: input.discountCode ?? null,
390
- notes: input.notes ?? null,
391
- shippingAddress: null,
392
- // CreateOrderInput has no payment method — that is a PlaceOrderInput
393
- // concern; the mock records none rather than inventing one.
394
- paymentMethodKind: null,
395
- paidAt: null,
396
- items: input.items.map((i) => ({ id: uid(), orderId, productId: i.productId ?? null, name: i.name, sku: i.sku ?? null, quantity: i.quantity, unitPrice: i.unitPrice, total: Math.round(i.quantity * i.unitPrice * 100) / 100, imageUrl: i.imageUrl ?? null })),
397
- createdAt: now(),
398
- updatedAt: now()
399
- };
400
- this.orders.push(o);
401
- this.persistOrders();
402
- return o;
403
- }
404
- async updateOrder(id, input) {
405
- this.syncOrders();
406
- const i = this.orders.findIndex((o) => o.id === id);
407
- if (i < 0) throw new Error("Order not found");
408
- this.orders[i] = { ...this.orders[i], ...input, updatedAt: now() };
409
- this.persistOrders();
410
- return this.orders[i];
411
- }
412
- // Trusted placement (mock parity with the shop_place_order RPC): re-reads
413
- // prices + stock from the catalog, validates the discount, decrements
414
- // inventory. The browser-supplied prices are never used.
415
- async placeOrder(input) {
416
- this.syncOrders();
417
- this.syncCustomers();
418
- let customerId = input.customerId ?? null;
419
- let customerName = input.customer?.name?.trim() || null;
420
- const email = input.customer?.email?.trim().toLowerCase() || null;
421
- if (!customerId && email) {
422
- const existing = this.customers.find((c) => (c.email ?? "").toLowerCase() === email);
423
- if (existing) {
424
- customerId = existing.id;
425
- customerName = customerName ?? `${existing.firstName} ${existing.lastName}`.trim();
426
- } else {
427
- const [first, ...rest] = (customerName ?? email).split(" ");
428
- const created = await this.createCustomer({ firstName: first || email, lastName: rest.join(" "), email });
429
- customerId = created.id;
430
- }
431
- }
432
- const items = [];
433
- let subtotal = 0;
434
- for (const line of input.items) {
435
- const product = this.products.find((p) => p.id === line.productId);
436
- if (!product || product.status !== "active") throw new Error(`Product unavailable: ${line.productId}`);
437
- if (product.inventoryCount < line.quantity) throw new Error(`Insufficient stock for ${product.name}`);
438
- const name = line.optionsLabel ? `${product.name} (${line.optionsLabel})` : product.name;
439
- subtotal += product.price * line.quantity;
440
- items.push({ productId: product.id, name, sku: product.sku ?? void 0, quantity: line.quantity, unitPrice: product.price, imageUrl: product.images[0]?.url });
441
- }
442
- subtotal = Math.round(subtotal * 100) / 100;
443
- let shippingTotal = Math.max(input.shippingTotal ?? 0, 0);
444
- let discountTotal = 0;
445
- let appliedCode = null;
446
- if (input.discountCode) {
447
- const code = input.discountCode.trim().toLowerCase();
448
- const d = this.discounts.find((x) => (x.code ?? "").toLowerCase() === code);
449
- const ts = now();
450
- const valid = !!d && d.status === "active" && d.startsAt <= ts && (!d.endsAt || d.endsAt >= ts) && (d.usageLimit == null || d.timesUsed < d.usageLimit);
451
- if (valid && d) {
452
- appliedCode = d.code;
453
- if (d.type === "percentage") discountTotal = Math.round(subtotal * d.value) / 100;
454
- else if (d.type === "fixed_amount") discountTotal = Math.min(d.value, subtotal);
455
- else if (d.type === "free_shipping") shippingTotal = 0;
456
- d.timesUsed += 1;
457
- }
458
- }
459
- discountTotal = Math.round(Math.min(Math.max(discountTotal, 0), subtotal) * 100) / 100;
460
- const order = await this.createOrder({
461
- customerId: customerId ?? void 0,
462
- customerName: customerName ?? void 0,
463
- customerEmail: input.customer?.email ?? void 0,
464
- currency: input.currency,
465
- notes: input.notes,
466
- discountCode: appliedCode ?? void 0,
467
- discountTotal,
468
- shippingTotal,
469
- items
470
- });
471
- for (const line of input.items) {
472
- const product = this.products.find((p) => p.id === line.productId);
473
- if (product) product.inventoryCount = Math.max(0, product.inventoryCount - line.quantity);
474
- }
475
- return order;
476
- }
477
- // ---------------------------------------------------------------------------
478
- // Customers
479
- // ---------------------------------------------------------------------------
480
- async listCustomers(opts) {
481
- this.syncCustomers();
482
- let list = [...this.customers];
483
- if (opts?.search) list = list.filter((c) => `${c.firstName} ${c.lastName} ${c.email}`.toLowerCase().includes(opts.search.toLowerCase()));
484
- if (opts?.limit) list = list.slice(0, opts.limit);
485
- return list;
486
- }
487
- async getCustomer(id) {
488
- this.syncCustomers();
489
- return this.customers.find((c) => c.id === id) ?? null;
490
- }
491
- async createCustomer(input) {
492
- this.syncCustomers();
493
- const c = { id: uid(), tenantId: "mock", firstName: input.firstName, lastName: input.lastName ?? "", email: input.email ?? null, phone: input.phone ?? null, notes: input.notes ?? null, ordersCount: 0, totalSpent: 0, createdAt: now(), updatedAt: now() };
494
- this.customers.push(c);
495
- this.persistCustomers();
496
- return c;
497
- }
498
- async updateCustomer(id, input) {
499
- this.syncCustomers();
500
- const i = this.customers.findIndex((c) => c.id === id);
501
- if (i < 0) throw new Error("Customer not found");
502
- this.customers[i] = { ...this.customers[i], ...input, updatedAt: now() };
503
- this.persistCustomers();
504
- return this.customers[i];
505
- }
506
- async deleteCustomer(id) {
507
- this.syncCustomers();
508
- this.customers = this.customers.filter((c) => c.id !== id);
509
- this.persistCustomers();
510
- }
511
- async resolveCustomer(input) {
512
- this.syncCustomers();
513
- const email = input.email.trim().toLowerCase();
514
- const existing = this.customers.find((c) => (c.email ?? "").toLowerCase() === email);
515
- if (existing) return existing;
516
- const [first, ...rest] = (input.name?.trim() || email).split(/\s+/);
517
- return this.createCustomer({ firstName: first || email, lastName: rest.join(" "), email });
518
- }
519
- // ---------------------------------------------------------------------------
520
- // Discounts
521
- // ---------------------------------------------------------------------------
522
- async listDiscounts(opts) {
523
- let list = [...this.discounts];
524
- if (opts?.status) list = list.filter((d) => d.status === opts.status);
525
- if (opts?.type) list = list.filter((d) => d.type === opts.type);
526
- if (opts?.search) list = list.filter((d) => d.title.toLowerCase().includes(opts.search.toLowerCase()));
527
- if (opts?.limit) list = list.slice(0, opts.limit);
528
- return list;
529
- }
530
- async getDiscount(id) {
531
- return this.discounts.find((d) => d.id === id) ?? null;
532
- }
533
- async createDiscount(input) {
534
- const d = { id: uid(), tenantId: "mock", title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? "code", value: input.value, usageLimit: input.usageLimit ?? null, oncePerCustomer: input.oncePerCustomer ?? false, startsAt: input.startsAt ?? now(), endsAt: input.endsAt ?? null, status: input.status ?? "active", timesUsed: 0, createdAt: now(), updatedAt: now() };
535
- this.discounts.push(d);
536
- return d;
537
- }
538
- async updateDiscount(id, input) {
539
- const i = this.discounts.findIndex((d) => d.id === id);
540
- if (i < 0) throw new Error("Discount not found");
541
- this.discounts[i] = { ...this.discounts[i], ...input, updatedAt: now() };
542
- return this.discounts[i];
543
- }
544
- async deleteDiscount(id) {
545
- this.discounts = this.discounts.filter((d) => d.id !== id);
546
- }
547
- };
548
- function createMockShopProvider(seed) {
549
- return new MockShopProvider(seed);
550
- }
551
-
552
- // src/runtime.ts
553
- var provider = null;
554
- var mockFallback = null;
555
- function getShopProvider() {
556
- if (provider) return provider;
557
- if (!mockFallback) mockFallback = createMockShopProvider();
558
- return mockFallback;
559
- }
560
- function setShopProvider(nextProvider) {
561
- provider = nextProvider;
562
- }
563
- function resetShopProvider() {
564
- provider = null;
565
- mockFallback = null;
566
- }
567
-
568
8
  // src/commerce.ts
569
9
  function round2(n) {
570
10
  return Math.round(n * 100) / 100;
571
11
  }
572
- async function validateDiscount(input, provider2 = getShopProvider()) {
12
+ async function validateDiscount(input, provider = getShopProvider()) {
573
13
  const code = input.code.trim();
574
14
  if (!code) return { valid: false, code: null, type: null, value: 0, reason: "empty" };
575
- const list = await provider2.listDiscounts({ status: "active" });
15
+ const list = await provider.listDiscounts({ status: "active" });
576
16
  const discount = list.find((d) => (d.code ?? "").toUpperCase() === code.toUpperCase());
577
17
  if (!discount) return { valid: false, code: null, type: null, value: 0, reason: "not_found" };
578
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
579
- if (discount.startsAt > now2) return { valid: false, code: discount.code, type: discount.type, value: 0, reason: "not_started" };
580
- if (discount.endsAt && discount.endsAt < now2) return { valid: false, code: discount.code, type: discount.type, value: 0, reason: "expired" };
18
+ const now = (/* @__PURE__ */ new Date()).toISOString();
19
+ if (discount.startsAt > now) return { valid: false, code: discount.code, type: discount.type, value: 0, reason: "not_started" };
20
+ if (discount.endsAt && discount.endsAt < now) return { valid: false, code: discount.code, type: discount.type, value: 0, reason: "expired" };
581
21
  if (discount.usageLimit != null && discount.timesUsed >= discount.usageLimit) {
582
22
  return { valid: false, code: discount.code, type: discount.type, value: 0, reason: "usage_limit" };
583
23
  }
@@ -596,10 +36,10 @@ function applyDiscount(validation, subtotal) {
596
36
  }
597
37
  return { discountTotal: 0, code: validation.code };
598
38
  }
599
- async function checkInventory(lines, provider2 = getShopProvider()) {
39
+ async function checkInventory(lines, provider = getShopProvider()) {
600
40
  const issues = [];
601
41
  for (const line of lines) {
602
- const product = await provider2.getProduct(line.productId);
42
+ const product = await provider.getProduct(line.productId);
603
43
  if (!product || product.status !== "active") {
604
44
  issues.push({ productId: line.productId, name: product?.name ?? line.productId, requested: line.quantity, available: 0 });
605
45
  continue;
@@ -610,11 +50,11 @@ async function checkInventory(lines, provider2 = getShopProvider()) {
610
50
  }
611
51
  return { ok: issues.length === 0, issues };
612
52
  }
613
- async function computeCartTotals(input, provider2 = getShopProvider()) {
53
+ async function computeCartTotals(input, provider = getShopProvider()) {
614
54
  const lines = [];
615
55
  let subtotal = 0;
616
56
  for (const line of input.lines) {
617
- const product = await provider2.getProduct(line.productId);
57
+ const product = await provider.getProduct(line.productId);
618
58
  if (!product) continue;
619
59
  const lineTotal = round2(product.price * line.quantity);
620
60
  subtotal += lineTotal;
@@ -625,7 +65,7 @@ async function computeCartTotals(input, provider2 = getShopProvider()) {
625
65
  let discountCode = null;
626
66
  if (input.discountCode) {
627
67
  const applied = applyDiscount(
628
- await validateDiscount({ code: input.discountCode, customerId: input.customerId }, provider2),
68
+ await validateDiscount({ code: input.discountCode, customerId: input.customerId }, provider),
629
69
  subtotal
630
70
  );
631
71
  discountTotal = applied.discountTotal;
@@ -636,8 +76,8 @@ async function computeCartTotals(input, provider2 = getShopProvider()) {
636
76
  const total = round2(Math.max(subtotal - discountTotal + shippingTotal + taxTotal, 0));
637
77
  return { lines, subtotal, discountTotal, discountCode, taxTotal, shippingTotal, total };
638
78
  }
639
- async function getCustomerOrders(customerId, provider2 = getShopProvider()) {
640
- return provider2.listOrders({ customerId, limit: 100 });
79
+ async function getCustomerOrders(customerId, provider = getShopProvider()) {
80
+ return provider.listOrders({ customerId, limit: 100 });
641
81
  }
642
82
 
643
83
  // src/tenant.ts
@@ -663,6 +103,7 @@ var T = {
663
103
  orderItems: "plg_shop_order_items",
664
104
  customers: "plg_shop_customers",
665
105
  discounts: "plg_shop_discounts",
106
+ salesChannels: "plg_shop_channels",
666
107
  // Core, not plg_shop_*: a delivery zone is a fact about the business, and
667
108
  // fulfilment and financial read it too. Same reasoning as addresses and
668
109
  // orders — see migration 0021.
@@ -831,6 +272,10 @@ function rowToDiscount(row) {
831
272
  value: row.value ?? 0,
832
273
  usageLimit: row.usage_limit,
833
274
  oncePerCustomer: row.once_per_customer ?? false,
275
+ minSubtotal: row.min_subtotal ?? null,
276
+ appliesTo: row.applies_to ?? "all",
277
+ productIds: row.product_ids ?? [],
278
+ categoryIds: row.category_ids ?? [],
834
279
  startsAt: row.starts_at,
835
280
  endsAt: row.ends_at,
836
281
  status: row.status ?? "active",
@@ -839,6 +284,19 @@ function rowToDiscount(row) {
839
284
  updatedAt: row.updated_at
840
285
  };
841
286
  }
287
+ function rowToSalesChannel(row) {
288
+ return {
289
+ id: row.id,
290
+ tenantId: row.tenant_id,
291
+ kind: row.kind,
292
+ label: row.label,
293
+ url: row.url,
294
+ enabled: row.enabled ?? true,
295
+ position: row.position ?? 0,
296
+ createdAt: row.created_at,
297
+ updatedAt: row.updated_at
298
+ };
299
+ }
842
300
  var SupabaseShopProvider = class {
843
301
  // ---------------------------------------------------------------------------
844
302
  // Products
@@ -1199,7 +657,7 @@ var SupabaseShopProvider = class {
1199
657
  return data ? rowToDiscount(data) : null;
1200
658
  }
1201
659
  async createDiscount(input) {
1202
- const { data, error } = await getDb().from(T.discounts).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 ?? (/* @__PURE__ */ new Date()).toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? "active", times_used: 0 }).select().single();
660
+ const { data, error } = await getDb().from(T.discounts).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 ?? (/* @__PURE__ */ new Date()).toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? "active", times_used: 0 }).select().single();
1203
661
  if (error) throw error;
1204
662
  return rowToDiscount(data);
1205
663
  }
@@ -1210,6 +668,10 @@ var SupabaseShopProvider = class {
1210
668
  if (input.type !== void 0) updates.type = input.type;
1211
669
  if (input.value !== void 0) updates.value = input.value;
1212
670
  if (input.usageLimit !== void 0) updates.usage_limit = input.usageLimit;
671
+ if (input.minSubtotal !== void 0) updates.min_subtotal = input.minSubtotal;
672
+ if (input.appliesTo !== void 0) updates.applies_to = input.appliesTo;
673
+ if (input.productIds !== void 0) updates.product_ids = input.productIds;
674
+ if (input.categoryIds !== void 0) updates.category_ids = input.categoryIds;
1213
675
  if (input.status !== void 0) updates.status = input.status;
1214
676
  if (input.endsAt !== void 0) updates.ends_at = input.endsAt;
1215
677
  const { data, error } = await getDb().from(T.discounts).update(updates).eq("id", id).select().single();
@@ -1220,11 +682,43 @@ var SupabaseShopProvider = class {
1220
682
  const { error } = await getDb().from(T.discounts).delete().eq("id", id);
1221
683
  if (error) throw error;
1222
684
  }
685
+ // ---------------------------------------------------------------------------
686
+ // Sales channels
687
+ // ---------------------------------------------------------------------------
688
+ async listSalesChannels() {
689
+ const db = getDb();
690
+ const tenantId = getTenantId();
691
+ let q = db.from(T.salesChannels).select("*").order("position", { ascending: true });
692
+ if (tenantId) q = q.eq("tenant_id", tenantId);
693
+ const { data, error } = await q;
694
+ if (error) throw error;
695
+ return (data ?? []).map(rowToSalesChannel);
696
+ }
697
+ async createSalesChannel(input) {
698
+ const { data, error } = await getDb().from(T.salesChannels).insert({ tenant_id: getTenantId(), kind: input.kind, label: input.label, url: input.url ?? null, enabled: input.enabled ?? true, position: input.position ?? 0 }).select().single();
699
+ if (error) throw error;
700
+ return rowToSalesChannel(data);
701
+ }
702
+ async updateSalesChannel(id, input) {
703
+ const updates = {};
704
+ if (input.kind !== void 0) updates.kind = input.kind;
705
+ if (input.label !== void 0) updates.label = input.label;
706
+ if (input.url !== void 0) updates.url = input.url;
707
+ if (input.enabled !== void 0) updates.enabled = input.enabled;
708
+ if (input.position !== void 0) updates.position = input.position;
709
+ const { data, error } = await getDb().from(T.salesChannels).update(updates).eq("id", id).select().single();
710
+ if (error) throw error;
711
+ return rowToSalesChannel(data);
712
+ }
713
+ async deleteSalesChannel(id) {
714
+ const { error } = await getDb().from(T.salesChannels).delete().eq("id", id);
715
+ if (error) throw error;
716
+ }
1223
717
  };
1224
718
  function createSupabaseShopProvider() {
1225
719
  return new SupabaseShopProvider();
1226
720
  }
1227
721
 
1228
- export { MOCK_CATEGORIES, MOCK_DISCOUNTS, MOCK_PRODUCTS, MockShopProvider, SupabaseShopProvider, applyDiscount, buildMockCatalog, checkInventory, computeCartTotals, createMockShopProvider, createSupabaseShopProvider, getCustomerOrders, getShopProvider, getShopTenantId, resetShopProvider, setShopProvider, setShopTenantResolver, svgPlaceholder, validateDiscount };
722
+ export { SupabaseShopProvider, applyDiscount, checkInventory, computeCartTotals, createSupabaseShopProvider, getCustomerOrders, getShopTenantId, setShopTenantResolver, validateDiscount };
1229
723
  //# sourceMappingURL=index.js.map
1230
724
  //# sourceMappingURL=index.js.map