@fayz-ai/shop 0.8.4 → 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 -176
- package/dist/catalog.js.map +1 -1
- package/dist/{catalog.cjs → chunk-CTKZ6O2J.js} +3 -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} +4 -102
- package/dist/chunk-SVTCJ6HP.js.map +1 -0
- package/dist/index.js +18 -639
- package/dist/index.js.map +1 -1
- package/dist/mock.js +2 -533
- package/dist/mock.js.map +1 -1
- package/dist/runtime.js +3 -549
- package/dist/runtime.js.map +1 -1
- package/package.json +22 -26
- package/dist/catalog.cjs.map +0 -1
- package/dist/index.cjs +0 -1365
- 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 -555
- package/dist/runtime.cjs.map +0 -1
- package/dist/types.cjs +0 -4
- package/dist/types.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,644 +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
|
-
minSubtotal: null,
|
|
88
|
-
appliesTo: "all",
|
|
89
|
-
productIds: [],
|
|
90
|
-
categoryIds: [],
|
|
91
|
-
startsAt: T0,
|
|
92
|
-
endsAt: null,
|
|
93
|
-
status: "active",
|
|
94
|
-
timesUsed: 0,
|
|
95
|
-
createdAt: T0,
|
|
96
|
-
updatedAt: T0
|
|
97
|
-
}
|
|
98
|
-
];
|
|
99
|
-
function slugifyName(name) {
|
|
100
|
-
return name.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
101
|
-
}
|
|
102
|
-
function buildMockCatalog(input) {
|
|
103
|
-
const categories = input.categories.map((c, i) => ({
|
|
104
|
-
id: `cat-${slugifyName(c.name)}`,
|
|
105
|
-
tenantId: "mock",
|
|
106
|
-
name: c.name,
|
|
107
|
-
slug: slugifyName(c.name),
|
|
108
|
-
description: c.description ?? null,
|
|
109
|
-
parentId: null,
|
|
110
|
-
sortOrder: i,
|
|
111
|
-
imageUrl: c.image ?? null,
|
|
112
|
-
createdAt: T0
|
|
113
|
-
}));
|
|
114
|
-
const byName = Object.fromEntries(categories.map((c) => [c.name, c]));
|
|
115
|
-
const products = input.products.map((p, i) => {
|
|
116
|
-
const cat = byName[p.category];
|
|
117
|
-
const id = `p-${String(i + 1).padStart(2, "0")}`;
|
|
118
|
-
const hues = p.hues ?? [i * 47 % 360, (i * 47 + 50) % 360];
|
|
119
|
-
const stamp = `2026-01-${String(i % 27 + 1).padStart(2, "0")}T12:00:00.000Z`;
|
|
120
|
-
return {
|
|
121
|
-
id,
|
|
122
|
-
tenantId: "mock",
|
|
123
|
-
name: p.name,
|
|
124
|
-
slug: slugifyName(p.name),
|
|
125
|
-
description: p.description,
|
|
126
|
-
price: p.price,
|
|
127
|
-
compareAtPrice: p.compareAtPrice ?? null,
|
|
128
|
-
currency: input.currency ?? "BRL",
|
|
129
|
-
status: "active",
|
|
130
|
-
inventoryCount: p.inventory,
|
|
131
|
-
sku: p.sku,
|
|
132
|
-
sortOrder: i,
|
|
133
|
-
metadata: p.metadata ?? {},
|
|
134
|
-
images: [
|
|
135
|
-
{
|
|
136
|
-
id: `img-${id}-0`,
|
|
137
|
-
productId: id,
|
|
138
|
-
url: p.image ?? svgPlaceholder(p.name, hues[0], hues[1]),
|
|
139
|
-
altText: p.name,
|
|
140
|
-
sortOrder: 0,
|
|
141
|
-
isPrimary: true,
|
|
142
|
-
createdAt: T0
|
|
143
|
-
}
|
|
144
|
-
],
|
|
145
|
-
categoryId: cat?.id ?? null,
|
|
146
|
-
categoryName: cat?.name ?? null,
|
|
147
|
-
isPhysical: true,
|
|
148
|
-
weight: p.weight ?? 0.5,
|
|
149
|
-
weightUnit: "kg",
|
|
150
|
-
createdAt: stamp,
|
|
151
|
-
updatedAt: stamp
|
|
152
|
-
};
|
|
153
|
-
});
|
|
154
|
-
const discounts = (input.discounts ?? []).map((d, i) => ({
|
|
155
|
-
id: `disc-${slugifyName(d.code)}`,
|
|
156
|
-
tenantId: "mock",
|
|
157
|
-
title: d.title ?? `${d.percent}% off`,
|
|
158
|
-
code: d.code,
|
|
159
|
-
type: "percentage",
|
|
160
|
-
method: "code",
|
|
161
|
-
value: d.percent,
|
|
162
|
-
usageLimit: null,
|
|
163
|
-
oncePerCustomer: false,
|
|
164
|
-
minSubtotal: null,
|
|
165
|
-
appliesTo: "all",
|
|
166
|
-
productIds: [],
|
|
167
|
-
categoryIds: [],
|
|
168
|
-
startsAt: T0,
|
|
169
|
-
endsAt: null,
|
|
170
|
-
status: "active",
|
|
171
|
-
timesUsed: 0,
|
|
172
|
-
createdAt: T0,
|
|
173
|
-
updatedAt: T0
|
|
174
|
-
}));
|
|
175
|
-
return { products, categories, discounts };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// src/mock-provider.ts
|
|
179
|
-
function uid() {
|
|
180
|
-
return Math.random().toString(36).slice(2);
|
|
181
|
-
}
|
|
182
|
-
function now() {
|
|
183
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
184
|
-
}
|
|
185
|
-
var ORDERS_KEY = "fayz.shop.mock.orders.v1";
|
|
186
|
-
var CUSTOMERS_KEY = "fayz.shop.mock.customers.v1";
|
|
187
|
-
var ENQUIRIES_KEY = "fayz.shop.mock.enquiries.v1";
|
|
188
|
-
var CHANNELS_KEY = "fayz.shop.mock.channels.v1";
|
|
189
|
-
var DEFAULT_CHANNELS = [
|
|
190
|
-
{ id: "online-store", tenantId: "mock", kind: "online_store", label: "Loja Online", url: null, enabled: true, position: 0, createdAt: now(), updatedAt: now() }
|
|
191
|
-
];
|
|
192
|
-
function loadStored(key) {
|
|
193
|
-
try {
|
|
194
|
-
if (typeof localStorage === "undefined") return [];
|
|
195
|
-
const raw = localStorage.getItem(key);
|
|
196
|
-
return raw ? JSON.parse(raw) : [];
|
|
197
|
-
} catch {
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
function store(key, value) {
|
|
202
|
-
try {
|
|
203
|
-
if (typeof localStorage === "undefined") return;
|
|
204
|
-
localStorage.setItem(key, JSON.stringify(value));
|
|
205
|
-
} catch {
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
var MockShopProvider = class {
|
|
209
|
-
constructor(seed) {
|
|
210
|
-
this.orders = loadStored(ORDERS_KEY);
|
|
211
|
-
this.customers = loadStored(CUSTOMERS_KEY);
|
|
212
|
-
this.enquiries = loadStored(ENQUIRIES_KEY);
|
|
213
|
-
this.channels = (() => {
|
|
214
|
-
const stored = loadStored(CHANNELS_KEY);
|
|
215
|
-
return stored.length ? stored : DEFAULT_CHANNELS.map((c) => ({ ...c }));
|
|
216
|
-
})();
|
|
217
|
-
this.products = (seed?.products ?? MOCK_PRODUCTS).map((p) => ({ ...p }));
|
|
218
|
-
this.categories = (seed?.categories ?? MOCK_CATEGORIES).map((c) => ({ ...c }));
|
|
219
|
-
this.discounts = (seed?.discounts ?? MOCK_DISCOUNTS).map((d) => ({ ...d }));
|
|
220
|
-
}
|
|
221
|
-
// Re-read before mutating: HMR can re-instantiate the provider mid-session
|
|
222
|
-
// and a stale in-memory copy must not clobber persisted records.
|
|
223
|
-
syncOrders() {
|
|
224
|
-
this.orders = loadStored(ORDERS_KEY);
|
|
225
|
-
}
|
|
226
|
-
syncCustomers() {
|
|
227
|
-
this.customers = loadStored(CUSTOMERS_KEY);
|
|
228
|
-
}
|
|
229
|
-
syncEnquiries() {
|
|
230
|
-
this.enquiries = loadStored(ENQUIRIES_KEY);
|
|
231
|
-
}
|
|
232
|
-
syncChannels() {
|
|
233
|
-
const stored = loadStored(CHANNELS_KEY);
|
|
234
|
-
if (stored.length) this.channels = stored;
|
|
235
|
-
}
|
|
236
|
-
persistOrders() {
|
|
237
|
-
store(ORDERS_KEY, this.orders);
|
|
238
|
-
}
|
|
239
|
-
persistCustomers() {
|
|
240
|
-
store(CUSTOMERS_KEY, this.customers);
|
|
241
|
-
}
|
|
242
|
-
persistEnquiries() {
|
|
243
|
-
store(ENQUIRIES_KEY, this.enquiries);
|
|
244
|
-
}
|
|
245
|
-
persistChannels() {
|
|
246
|
-
store(CHANNELS_KEY, this.channels);
|
|
247
|
-
}
|
|
248
|
-
// ---------------------------------------------------------------------------
|
|
249
|
-
// Products
|
|
250
|
-
// ---------------------------------------------------------------------------
|
|
251
|
-
async listProducts(opts) {
|
|
252
|
-
let list = [...this.products];
|
|
253
|
-
if (opts?.status) list = list.filter((p) => p.status === opts.status);
|
|
254
|
-
if (opts?.categoryId) list = list.filter((p) => p.categoryId === opts.categoryId);
|
|
255
|
-
if (opts?.slug) list = list.filter((p) => p.slug === opts.slug);
|
|
256
|
-
if (opts?.search) {
|
|
257
|
-
const q = opts.search.toLowerCase();
|
|
258
|
-
list = list.filter(
|
|
259
|
-
(p) => p.name.toLowerCase().includes(q) || (p.description ?? "").toLowerCase().includes(q) || (p.sku ?? "").toLowerCase().includes(q)
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
const orderBy = opts?.orderBy ?? "sort_order";
|
|
263
|
-
const dir = opts?.order === "desc" ? -1 : 1;
|
|
264
|
-
list.sort((a, b) => {
|
|
265
|
-
switch (orderBy) {
|
|
266
|
-
case "name":
|
|
267
|
-
return a.name.localeCompare(b.name) * dir;
|
|
268
|
-
case "price":
|
|
269
|
-
return (a.price - b.price) * dir;
|
|
270
|
-
case "created_at":
|
|
271
|
-
return a.createdAt.localeCompare(b.createdAt) * dir;
|
|
272
|
-
default:
|
|
273
|
-
return (a.sortOrder - b.sortOrder) * dir;
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
const offset = opts?.offset ?? 0;
|
|
277
|
-
if (offset || opts?.limit) list = list.slice(offset, opts?.limit ? offset + opts.limit : void 0);
|
|
278
|
-
return list;
|
|
279
|
-
}
|
|
280
|
-
async getProduct(id) {
|
|
281
|
-
return this.products.find((p) => p.id === id) ?? null;
|
|
282
|
-
}
|
|
283
|
-
async createProduct(input) {
|
|
284
|
-
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() };
|
|
285
|
-
this.products.push(p);
|
|
286
|
-
return p;
|
|
287
|
-
}
|
|
288
|
-
async updateProduct(id, input) {
|
|
289
|
-
const i = this.products.findIndex((p) => p.id === id);
|
|
290
|
-
if (i < 0) throw new Error("Product not found");
|
|
291
|
-
this.products[i] = { ...this.products[i], ...input, updatedAt: now() };
|
|
292
|
-
return this.products[i];
|
|
293
|
-
}
|
|
294
|
-
async deleteProduct(id) {
|
|
295
|
-
this.products = this.products.filter((p) => p.id !== id);
|
|
296
|
-
}
|
|
297
|
-
async uploadProductImage(_productId, _file) {
|
|
298
|
-
throw new Error("Image upload not available in mock mode");
|
|
299
|
-
}
|
|
300
|
-
async deleteProductImage(_imageId) {
|
|
301
|
-
}
|
|
302
|
-
async createProductEnquiry(input) {
|
|
303
|
-
this.syncEnquiries();
|
|
304
|
-
const ts = now();
|
|
305
|
-
const enquiry = {
|
|
306
|
-
id: uid(),
|
|
307
|
-
tenantId: "mock",
|
|
308
|
-
productId: input.productId ?? null,
|
|
309
|
-
productName: input.productName,
|
|
310
|
-
productSlug: input.productSlug ?? null,
|
|
311
|
-
customerName: input.customerName,
|
|
312
|
-
customerEmail: input.customerEmail,
|
|
313
|
-
customerPhone: input.customerPhone ?? null,
|
|
314
|
-
subject: input.subject ?? null,
|
|
315
|
-
message: input.message,
|
|
316
|
-
sourceUrl: input.sourceUrl ?? null,
|
|
317
|
-
status: "new",
|
|
318
|
-
metadata: input.metadata ?? {},
|
|
319
|
-
createdAt: ts,
|
|
320
|
-
updatedAt: ts
|
|
321
|
-
};
|
|
322
|
-
this.enquiries.unshift(enquiry);
|
|
323
|
-
this.persistEnquiries();
|
|
324
|
-
return enquiry;
|
|
325
|
-
}
|
|
326
|
-
async listProductEnquiries(opts) {
|
|
327
|
-
this.syncEnquiries();
|
|
328
|
-
let list = [...this.enquiries];
|
|
329
|
-
if (opts?.productId) list = list.filter((e) => e.productId === opts.productId);
|
|
330
|
-
if (opts?.customerEmail) {
|
|
331
|
-
const email = opts.customerEmail.toLowerCase();
|
|
332
|
-
list = list.filter((e) => e.customerEmail.toLowerCase() === email);
|
|
333
|
-
}
|
|
334
|
-
if (opts?.status) list = list.filter((e) => e.status === opts.status);
|
|
335
|
-
list.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
336
|
-
const offset = opts?.offset ?? 0;
|
|
337
|
-
if (offset || opts?.limit) list = list.slice(offset, opts?.limit ? offset + opts.limit : void 0);
|
|
338
|
-
return list;
|
|
339
|
-
}
|
|
340
|
-
// ---------------------------------------------------------------------------
|
|
341
|
-
// Categories
|
|
342
|
-
// ---------------------------------------------------------------------------
|
|
343
|
-
async listCategories() {
|
|
344
|
-
return [...this.categories].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
345
|
-
}
|
|
346
|
-
async createCategory(input) {
|
|
347
|
-
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() };
|
|
348
|
-
this.categories.push(c);
|
|
349
|
-
return c;
|
|
350
|
-
}
|
|
351
|
-
async updateCategory(id, input) {
|
|
352
|
-
const i = this.categories.findIndex((c) => c.id === id);
|
|
353
|
-
if (i < 0) throw new Error("Category not found");
|
|
354
|
-
this.categories[i] = { ...this.categories[i], ...input };
|
|
355
|
-
return this.categories[i];
|
|
356
|
-
}
|
|
357
|
-
async deleteCategory(id) {
|
|
358
|
-
this.categories = this.categories.filter((c) => c.id !== id);
|
|
359
|
-
}
|
|
360
|
-
// ---------------------------------------------------------------------------
|
|
361
|
-
// Orders
|
|
362
|
-
// ---------------------------------------------------------------------------
|
|
363
|
-
async listOrders(opts) {
|
|
364
|
-
this.syncOrders();
|
|
365
|
-
let list = [...this.orders];
|
|
366
|
-
if (opts?.status) list = list.filter((o) => o.status === opts.status);
|
|
367
|
-
if (opts?.financialStatus) list = list.filter((o) => o.financialStatus === opts.financialStatus);
|
|
368
|
-
if (opts?.fulfillmentStatus) list = list.filter((o) => o.fulfillmentStatus === opts.fulfillmentStatus);
|
|
369
|
-
if (opts?.customerId) list = list.filter((o) => o.customerId === opts.customerId);
|
|
370
|
-
if (opts?.customerEmail) {
|
|
371
|
-
const email = opts.customerEmail.toLowerCase();
|
|
372
|
-
list = list.filter((o) => (o.customerEmail ?? "").toLowerCase() === email);
|
|
373
|
-
}
|
|
374
|
-
if (opts?.search) {
|
|
375
|
-
const q = opts.search.toLowerCase();
|
|
376
|
-
list = list.filter(
|
|
377
|
-
(o) => (o.customerName ?? "").toLowerCase().includes(q) || (o.customerEmail ?? "").toLowerCase().includes(q) || String(o.orderNumber).includes(q)
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
list.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
381
|
-
if (opts?.limit) list = list.slice(0, opts.limit);
|
|
382
|
-
return list;
|
|
383
|
-
}
|
|
384
|
-
async getOrder(id) {
|
|
385
|
-
this.syncOrders();
|
|
386
|
-
return this.orders.find((o) => o.id === id) ?? null;
|
|
387
|
-
}
|
|
388
|
-
async createOrder(input) {
|
|
389
|
-
this.syncOrders();
|
|
390
|
-
const subtotal = input.items.reduce((s, i) => s + i.quantity * i.unitPrice, 0);
|
|
391
|
-
const discountTotal = input.discountTotal ?? 0;
|
|
392
|
-
const shippingTotal = input.shippingTotal ?? 0;
|
|
393
|
-
const total = Math.round((subtotal - discountTotal + shippingTotal) * 100) / 100;
|
|
394
|
-
const orderId = uid();
|
|
395
|
-
const maxNumber = this.orders.reduce((m, o2) => Math.max(m, o2.orderNumber), 1e3);
|
|
396
|
-
const o = {
|
|
397
|
-
id: orderId,
|
|
398
|
-
tenantId: "mock",
|
|
399
|
-
orderNumber: maxNumber + 1,
|
|
400
|
-
status: "open",
|
|
401
|
-
financialStatus: "pending",
|
|
402
|
-
fulfillmentStatus: "unfulfilled",
|
|
403
|
-
currency: input.currency ?? "BRL",
|
|
404
|
-
subtotal: Math.round(subtotal * 100) / 100,
|
|
405
|
-
taxTotal: 0,
|
|
406
|
-
discountTotal,
|
|
407
|
-
shippingTotal,
|
|
408
|
-
total,
|
|
409
|
-
customerId: input.customerId ?? null,
|
|
410
|
-
customerName: input.customerName ?? null,
|
|
411
|
-
customerEmail: input.customerEmail ?? null,
|
|
412
|
-
discountCode: input.discountCode ?? null,
|
|
413
|
-
notes: input.notes ?? null,
|
|
414
|
-
shippingAddress: null,
|
|
415
|
-
// CreateOrderInput has no payment method — that is a PlaceOrderInput
|
|
416
|
-
// concern; the mock records none rather than inventing one.
|
|
417
|
-
paymentMethodKind: null,
|
|
418
|
-
paidAt: null,
|
|
419
|
-
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 })),
|
|
420
|
-
createdAt: now(),
|
|
421
|
-
updatedAt: now()
|
|
422
|
-
};
|
|
423
|
-
this.orders.push(o);
|
|
424
|
-
this.persistOrders();
|
|
425
|
-
return o;
|
|
426
|
-
}
|
|
427
|
-
async updateOrder(id, input) {
|
|
428
|
-
this.syncOrders();
|
|
429
|
-
const i = this.orders.findIndex((o) => o.id === id);
|
|
430
|
-
if (i < 0) throw new Error("Order not found");
|
|
431
|
-
this.orders[i] = { ...this.orders[i], ...input, updatedAt: now() };
|
|
432
|
-
this.persistOrders();
|
|
433
|
-
return this.orders[i];
|
|
434
|
-
}
|
|
435
|
-
// Trusted placement (mock parity with the shop_place_order RPC): re-reads
|
|
436
|
-
// prices + stock from the catalog, validates the discount, decrements
|
|
437
|
-
// inventory. The browser-supplied prices are never used.
|
|
438
|
-
async placeOrder(input) {
|
|
439
|
-
this.syncOrders();
|
|
440
|
-
this.syncCustomers();
|
|
441
|
-
let customerId = input.customerId ?? null;
|
|
442
|
-
let customerName = input.customer?.name?.trim() || null;
|
|
443
|
-
const email = input.customer?.email?.trim().toLowerCase() || null;
|
|
444
|
-
if (!customerId && email) {
|
|
445
|
-
const existing = this.customers.find((c) => (c.email ?? "").toLowerCase() === email);
|
|
446
|
-
if (existing) {
|
|
447
|
-
customerId = existing.id;
|
|
448
|
-
customerName = customerName ?? `${existing.firstName} ${existing.lastName}`.trim();
|
|
449
|
-
} else {
|
|
450
|
-
const [first, ...rest] = (customerName ?? email).split(" ");
|
|
451
|
-
const created = await this.createCustomer({ firstName: first || email, lastName: rest.join(" "), email });
|
|
452
|
-
customerId = created.id;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
const items = [];
|
|
456
|
-
const lineDetails = [];
|
|
457
|
-
let subtotal = 0;
|
|
458
|
-
for (const line of input.items) {
|
|
459
|
-
const product = this.products.find((p) => p.id === line.productId);
|
|
460
|
-
if (!product || product.status !== "active") throw new Error(`Product unavailable: ${line.productId}`);
|
|
461
|
-
if (product.inventoryCount < line.quantity) throw new Error(`Insufficient stock for ${product.name}`);
|
|
462
|
-
const name = line.optionsLabel ? `${product.name} (${line.optionsLabel})` : product.name;
|
|
463
|
-
const lineTotal = product.price * line.quantity;
|
|
464
|
-
subtotal += lineTotal;
|
|
465
|
-
lineDetails.push({ productId: product.id, categoryId: product.categoryId, lineTotal });
|
|
466
|
-
items.push({ productId: product.id, name, sku: product.sku ?? void 0, quantity: line.quantity, unitPrice: product.price, imageUrl: product.images[0]?.url });
|
|
467
|
-
}
|
|
468
|
-
subtotal = Math.round(subtotal * 100) / 100;
|
|
469
|
-
let shippingTotal = Math.max(input.shippingTotal ?? 0, 0);
|
|
470
|
-
let discountTotal = 0;
|
|
471
|
-
let appliedCode = null;
|
|
472
|
-
if (input.discountCode) {
|
|
473
|
-
const code = input.discountCode.trim().toLowerCase();
|
|
474
|
-
const d = this.discounts.find((x) => (x.code ?? "").toLowerCase() === code);
|
|
475
|
-
const ts = now();
|
|
476
|
-
const meetsMin = !d?.minSubtotal || subtotal >= d.minSubtotal;
|
|
477
|
-
const valid = !!d && d.status === "active" && d.startsAt <= ts && (!d.endsAt || d.endsAt >= ts) && (d.usageLimit == null || d.timesUsed < d.usageLimit) && meetsMin;
|
|
478
|
-
if (valid && d) {
|
|
479
|
-
appliedCode = d.code;
|
|
480
|
-
let eligible = subtotal;
|
|
481
|
-
if (d.appliesTo === "products") {
|
|
482
|
-
eligible = lineDetails.filter((l) => d.productIds.includes(l.productId)).reduce((s, l) => s + l.lineTotal, 0);
|
|
483
|
-
} else if (d.appliesTo === "categories") {
|
|
484
|
-
eligible = lineDetails.filter((l) => l.categoryId && d.categoryIds.includes(l.categoryId)).reduce((s, l) => s + l.lineTotal, 0);
|
|
485
|
-
}
|
|
486
|
-
eligible = Math.round(eligible * 100) / 100;
|
|
487
|
-
if (d.type === "percentage") discountTotal = Math.round(eligible * d.value) / 100;
|
|
488
|
-
else if (d.type === "fixed_amount") discountTotal = Math.min(d.value, eligible);
|
|
489
|
-
else if (d.type === "free_shipping") shippingTotal = 0;
|
|
490
|
-
d.timesUsed += 1;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
discountTotal = Math.round(Math.min(Math.max(discountTotal, 0), subtotal) * 100) / 100;
|
|
494
|
-
const order = await this.createOrder({
|
|
495
|
-
customerId: customerId ?? void 0,
|
|
496
|
-
customerName: customerName ?? void 0,
|
|
497
|
-
customerEmail: input.customer?.email ?? void 0,
|
|
498
|
-
currency: input.currency,
|
|
499
|
-
notes: input.notes,
|
|
500
|
-
discountCode: appliedCode ?? void 0,
|
|
501
|
-
discountTotal,
|
|
502
|
-
shippingTotal,
|
|
503
|
-
items
|
|
504
|
-
});
|
|
505
|
-
for (const line of input.items) {
|
|
506
|
-
const product = this.products.find((p) => p.id === line.productId);
|
|
507
|
-
if (product) product.inventoryCount = Math.max(0, product.inventoryCount - line.quantity);
|
|
508
|
-
}
|
|
509
|
-
return order;
|
|
510
|
-
}
|
|
511
|
-
// ---------------------------------------------------------------------------
|
|
512
|
-
// Customers
|
|
513
|
-
// ---------------------------------------------------------------------------
|
|
514
|
-
async listCustomers(opts) {
|
|
515
|
-
this.syncCustomers();
|
|
516
|
-
let list = [...this.customers];
|
|
517
|
-
if (opts?.search) list = list.filter((c) => `${c.firstName} ${c.lastName} ${c.email}`.toLowerCase().includes(opts.search.toLowerCase()));
|
|
518
|
-
if (opts?.limit) list = list.slice(0, opts.limit);
|
|
519
|
-
return list;
|
|
520
|
-
}
|
|
521
|
-
async getCustomer(id) {
|
|
522
|
-
this.syncCustomers();
|
|
523
|
-
return this.customers.find((c) => c.id === id) ?? null;
|
|
524
|
-
}
|
|
525
|
-
async createCustomer(input) {
|
|
526
|
-
this.syncCustomers();
|
|
527
|
-
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() };
|
|
528
|
-
this.customers.push(c);
|
|
529
|
-
this.persistCustomers();
|
|
530
|
-
return c;
|
|
531
|
-
}
|
|
532
|
-
async updateCustomer(id, input) {
|
|
533
|
-
this.syncCustomers();
|
|
534
|
-
const i = this.customers.findIndex((c) => c.id === id);
|
|
535
|
-
if (i < 0) throw new Error("Customer not found");
|
|
536
|
-
this.customers[i] = { ...this.customers[i], ...input, updatedAt: now() };
|
|
537
|
-
this.persistCustomers();
|
|
538
|
-
return this.customers[i];
|
|
539
|
-
}
|
|
540
|
-
async deleteCustomer(id) {
|
|
541
|
-
this.syncCustomers();
|
|
542
|
-
this.customers = this.customers.filter((c) => c.id !== id);
|
|
543
|
-
this.persistCustomers();
|
|
544
|
-
}
|
|
545
|
-
async resolveCustomer(input) {
|
|
546
|
-
this.syncCustomers();
|
|
547
|
-
const email = input.email.trim().toLowerCase();
|
|
548
|
-
const existing = this.customers.find((c) => (c.email ?? "").toLowerCase() === email);
|
|
549
|
-
if (existing) return existing;
|
|
550
|
-
const [first, ...rest] = (input.name?.trim() || email).split(/\s+/);
|
|
551
|
-
return this.createCustomer({ firstName: first || email, lastName: rest.join(" "), email });
|
|
552
|
-
}
|
|
553
|
-
// ---------------------------------------------------------------------------
|
|
554
|
-
// Discounts
|
|
555
|
-
// ---------------------------------------------------------------------------
|
|
556
|
-
async listDiscounts(opts) {
|
|
557
|
-
let list = [...this.discounts];
|
|
558
|
-
if (opts?.status) list = list.filter((d) => d.status === opts.status);
|
|
559
|
-
if (opts?.type) list = list.filter((d) => d.type === opts.type);
|
|
560
|
-
if (opts?.search) list = list.filter((d) => d.title.toLowerCase().includes(opts.search.toLowerCase()));
|
|
561
|
-
if (opts?.limit) list = list.slice(0, opts.limit);
|
|
562
|
-
return list;
|
|
563
|
-
}
|
|
564
|
-
async getDiscount(id) {
|
|
565
|
-
return this.discounts.find((d) => d.id === id) ?? null;
|
|
566
|
-
}
|
|
567
|
-
async createDiscount(input) {
|
|
568
|
-
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, minSubtotal: input.minSubtotal ?? null, appliesTo: input.appliesTo ?? "all", productIds: input.productIds ?? [], categoryIds: input.categoryIds ?? [], startsAt: input.startsAt ?? now(), endsAt: input.endsAt ?? null, status: input.status ?? "active", timesUsed: 0, createdAt: now(), updatedAt: now() };
|
|
569
|
-
this.discounts.push(d);
|
|
570
|
-
return d;
|
|
571
|
-
}
|
|
572
|
-
async updateDiscount(id, input) {
|
|
573
|
-
const i = this.discounts.findIndex((d) => d.id === id);
|
|
574
|
-
if (i < 0) throw new Error("Discount not found");
|
|
575
|
-
this.discounts[i] = { ...this.discounts[i], ...input, updatedAt: now() };
|
|
576
|
-
return this.discounts[i];
|
|
577
|
-
}
|
|
578
|
-
async deleteDiscount(id) {
|
|
579
|
-
this.discounts = this.discounts.filter((d) => d.id !== id);
|
|
580
|
-
}
|
|
581
|
-
// ---------------------------------------------------------------------------
|
|
582
|
-
// Sales channels
|
|
583
|
-
// ---------------------------------------------------------------------------
|
|
584
|
-
async listSalesChannels() {
|
|
585
|
-
this.syncChannels();
|
|
586
|
-
return [...this.channels].sort((a, b) => a.position - b.position);
|
|
587
|
-
}
|
|
588
|
-
async createSalesChannel(input) {
|
|
589
|
-
this.syncChannels();
|
|
590
|
-
const c = { id: uid(), tenantId: "mock", kind: input.kind, label: input.label, url: input.url ?? null, enabled: input.enabled ?? true, position: input.position ?? this.channels.length, createdAt: now(), updatedAt: now() };
|
|
591
|
-
this.channels.push(c);
|
|
592
|
-
this.persistChannels();
|
|
593
|
-
return c;
|
|
594
|
-
}
|
|
595
|
-
async updateSalesChannel(id, input) {
|
|
596
|
-
this.syncChannels();
|
|
597
|
-
const i = this.channels.findIndex((c) => c.id === id);
|
|
598
|
-
if (i < 0) throw new Error("Sales channel not found");
|
|
599
|
-
this.channels[i] = { ...this.channels[i], ...input, updatedAt: now() };
|
|
600
|
-
this.persistChannels();
|
|
601
|
-
return this.channels[i];
|
|
602
|
-
}
|
|
603
|
-
async deleteSalesChannel(id) {
|
|
604
|
-
this.syncChannels();
|
|
605
|
-
this.channels = this.channels.filter((c) => c.id !== id);
|
|
606
|
-
this.persistChannels();
|
|
607
|
-
}
|
|
608
|
-
};
|
|
609
|
-
function createMockShopProvider(seed) {
|
|
610
|
-
return new MockShopProvider(seed);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// src/runtime.ts
|
|
614
|
-
var provider = null;
|
|
615
|
-
var mockFallback = null;
|
|
616
|
-
function getShopProvider() {
|
|
617
|
-
if (provider) return provider;
|
|
618
|
-
if (!mockFallback) mockFallback = createMockShopProvider();
|
|
619
|
-
return mockFallback;
|
|
620
|
-
}
|
|
621
|
-
function setShopProvider(nextProvider) {
|
|
622
|
-
provider = nextProvider;
|
|
623
|
-
}
|
|
624
|
-
function resetShopProvider() {
|
|
625
|
-
provider = null;
|
|
626
|
-
mockFallback = null;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
8
|
// src/commerce.ts
|
|
630
9
|
function round2(n) {
|
|
631
10
|
return Math.round(n * 100) / 100;
|
|
632
11
|
}
|
|
633
|
-
async function validateDiscount(input,
|
|
12
|
+
async function validateDiscount(input, provider = getShopProvider()) {
|
|
634
13
|
const code = input.code.trim();
|
|
635
14
|
if (!code) return { valid: false, code: null, type: null, value: 0, reason: "empty" };
|
|
636
|
-
const list = await
|
|
15
|
+
const list = await provider.listDiscounts({ status: "active" });
|
|
637
16
|
const discount = list.find((d) => (d.code ?? "").toUpperCase() === code.toUpperCase());
|
|
638
17
|
if (!discount) return { valid: false, code: null, type: null, value: 0, reason: "not_found" };
|
|
639
|
-
const
|
|
640
|
-
if (discount.startsAt >
|
|
641
|
-
if (discount.endsAt && discount.endsAt <
|
|
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" };
|
|
642
21
|
if (discount.usageLimit != null && discount.timesUsed >= discount.usageLimit) {
|
|
643
22
|
return { valid: false, code: discount.code, type: discount.type, value: 0, reason: "usage_limit" };
|
|
644
23
|
}
|
|
@@ -657,10 +36,10 @@ function applyDiscount(validation, subtotal) {
|
|
|
657
36
|
}
|
|
658
37
|
return { discountTotal: 0, code: validation.code };
|
|
659
38
|
}
|
|
660
|
-
async function checkInventory(lines,
|
|
39
|
+
async function checkInventory(lines, provider = getShopProvider()) {
|
|
661
40
|
const issues = [];
|
|
662
41
|
for (const line of lines) {
|
|
663
|
-
const product = await
|
|
42
|
+
const product = await provider.getProduct(line.productId);
|
|
664
43
|
if (!product || product.status !== "active") {
|
|
665
44
|
issues.push({ productId: line.productId, name: product?.name ?? line.productId, requested: line.quantity, available: 0 });
|
|
666
45
|
continue;
|
|
@@ -671,11 +50,11 @@ async function checkInventory(lines, provider2 = getShopProvider()) {
|
|
|
671
50
|
}
|
|
672
51
|
return { ok: issues.length === 0, issues };
|
|
673
52
|
}
|
|
674
|
-
async function computeCartTotals(input,
|
|
53
|
+
async function computeCartTotals(input, provider = getShopProvider()) {
|
|
675
54
|
const lines = [];
|
|
676
55
|
let subtotal = 0;
|
|
677
56
|
for (const line of input.lines) {
|
|
678
|
-
const product = await
|
|
57
|
+
const product = await provider.getProduct(line.productId);
|
|
679
58
|
if (!product) continue;
|
|
680
59
|
const lineTotal = round2(product.price * line.quantity);
|
|
681
60
|
subtotal += lineTotal;
|
|
@@ -686,7 +65,7 @@ async function computeCartTotals(input, provider2 = getShopProvider()) {
|
|
|
686
65
|
let discountCode = null;
|
|
687
66
|
if (input.discountCode) {
|
|
688
67
|
const applied = applyDiscount(
|
|
689
|
-
await validateDiscount({ code: input.discountCode, customerId: input.customerId },
|
|
68
|
+
await validateDiscount({ code: input.discountCode, customerId: input.customerId }, provider),
|
|
690
69
|
subtotal
|
|
691
70
|
);
|
|
692
71
|
discountTotal = applied.discountTotal;
|
|
@@ -697,8 +76,8 @@ async function computeCartTotals(input, provider2 = getShopProvider()) {
|
|
|
697
76
|
const total = round2(Math.max(subtotal - discountTotal + shippingTotal + taxTotal, 0));
|
|
698
77
|
return { lines, subtotal, discountTotal, discountCode, taxTotal, shippingTotal, total };
|
|
699
78
|
}
|
|
700
|
-
async function getCustomerOrders(customerId,
|
|
701
|
-
return
|
|
79
|
+
async function getCustomerOrders(customerId, provider = getShopProvider()) {
|
|
80
|
+
return provider.listOrders({ customerId, limit: 100 });
|
|
702
81
|
}
|
|
703
82
|
|
|
704
83
|
// src/tenant.ts
|
|
@@ -1340,6 +719,6 @@ function createSupabaseShopProvider() {
|
|
|
1340
719
|
return new SupabaseShopProvider();
|
|
1341
720
|
}
|
|
1342
721
|
|
|
1343
|
-
export {
|
|
722
|
+
export { SupabaseShopProvider, applyDiscount, checkInventory, computeCartTotals, createSupabaseShopProvider, getCustomerOrders, getShopTenantId, setShopTenantResolver, validateDiscount };
|
|
1344
723
|
//# sourceMappingURL=index.js.map
|
|
1345
724
|
//# sourceMappingURL=index.js.map
|