@fayz-ai/plugin-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/index.cjs DELETED
@@ -1,1188 +0,0 @@
1
- 'use strict';
2
-
3
- var React2 = require('react');
4
- var shop = require('@fayz-ai/shop');
5
- var saas = require('@fayz-ai/saas');
6
- var jsxRuntime = require('react/jsx-runtime');
7
-
8
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
-
10
- var React2__default = /*#__PURE__*/_interopDefault(React2);
11
-
12
- // src/index.ts
13
- function ProductImagesTab({ item, getProvider }) {
14
- const product = item;
15
- const [images, setImages] = React2.useState([]);
16
- const [busy, setBusy] = React2.useState(false);
17
- const [error, setError] = React2.useState(null);
18
- const fileRef = React2.useRef(null);
19
- async function reload() {
20
- const fresh = await getProvider().getProduct(product.id);
21
- setImages(sortImages(fresh?.images ?? []));
22
- }
23
- React2.useEffect(() => {
24
- void reload().catch(() => {
25
- });
26
- }, [product.id]);
27
- async function run(action) {
28
- setBusy(true);
29
- setError(null);
30
- try {
31
- await action();
32
- await reload();
33
- } catch (err) {
34
- setError(err instanceof Error ? err.message : String(err));
35
- } finally {
36
- setBusy(false);
37
- }
38
- }
39
- async function upload(files) {
40
- if (!files?.length) return;
41
- await run(async () => {
42
- for (const file of Array.from(files)) {
43
- await getProvider().uploadProductImage(product.id, file);
44
- }
45
- if (fileRef.current) fileRef.current.value = "";
46
- });
47
- }
48
- function move(index, delta) {
49
- const target = index + delta;
50
- if (target < 0 || target >= images.length) return;
51
- const a = images[index];
52
- const b = images[target];
53
- void run(async () => {
54
- const update2 = getProvider().updateProductImage;
55
- if (!update2) throw new Error("Este provider n\xE3o suporta reordenar imagens.");
56
- await update2(a.id, { sortOrder: b.sortOrder ?? target });
57
- await update2(b.id, { sortOrder: a.sortOrder ?? index });
58
- });
59
- }
60
- const update = getProvider().updateProductImage;
61
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
62
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
63
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
64
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: "Fotos do produto" }),
65
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "A primeira foto \xE9 a capa \u2014 aparece no cat\xE1logo, no carrinho e no pedido. As demais viram o carrossel na p\xE1gina do produto." })
66
- ] }),
67
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "inline-flex cursor-pointer items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition hover:opacity-90", children: [
68
- busy ? "Enviando\u2026" : "Adicionar fotos",
69
- /* @__PURE__ */ jsxRuntime.jsx(
70
- "input",
71
- {
72
- ref: fileRef,
73
- type: "file",
74
- accept: "image/*",
75
- multiple: true,
76
- disabled: busy,
77
- className: "hidden",
78
- onChange: (event) => void upload(event.target.files)
79
- }
80
- )
81
- ] })
82
- ] }),
83
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error }),
84
- images.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground", children: "Nenhuma foto ainda. Produtos com mais de uma foto vendem melhor \u2014 mostre a embalagem, o produto pronto e uma refer\xEAncia de tamanho." }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4", children: images.map((image, index) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
85
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative aspect-square bg-muted", children: [
86
- /* @__PURE__ */ jsxRuntime.jsx("img", { src: image.url, alt: image.altText ?? "", className: "h-full w-full object-cover" }),
87
- image.isPrimary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute left-2 top-2 rounded-full bg-primary px-2 py-0.5 text-[11px] font-bold text-primary-foreground", children: "Capa" })
88
- ] }),
89
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-1 px-2 py-1.5", children: [
90
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
91
- /* @__PURE__ */ jsxRuntime.jsx(
92
- IconButton,
93
- {
94
- label: "Mover para tr\xE1s",
95
- disabled: busy || index === 0,
96
- onClick: () => move(index, -1),
97
- children: "\u2190"
98
- }
99
- ),
100
- /* @__PURE__ */ jsxRuntime.jsx(
101
- IconButton,
102
- {
103
- label: "Mover para frente",
104
- disabled: busy || index === images.length - 1,
105
- onClick: () => move(index, 1),
106
- children: "\u2192"
107
- }
108
- )
109
- ] }),
110
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
111
- !image.isPrimary && update && /* @__PURE__ */ jsxRuntime.jsx(
112
- "button",
113
- {
114
- type: "button",
115
- disabled: busy,
116
- onClick: () => void run(() => update(image.id, { isPrimary: true })),
117
- className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50",
118
- children: "Usar como capa"
119
- }
120
- ),
121
- /* @__PURE__ */ jsxRuntime.jsx(
122
- "button",
123
- {
124
- type: "button",
125
- disabled: busy,
126
- onClick: () => void run(() => getProvider().deleteProductImage(image.id)),
127
- className: "rounded px-1.5 py-0.5 text-xs text-red-600 hover:bg-red-50 disabled:opacity-50",
128
- children: "Excluir"
129
- }
130
- )
131
- ] })
132
- ] })
133
- ] }, image.id)) })
134
- ] });
135
- }
136
- function IconButton({ label, disabled, onClick, children }) {
137
- return /* @__PURE__ */ jsxRuntime.jsx(
138
- "button",
139
- {
140
- type: "button",
141
- "aria-label": label,
142
- title: label,
143
- disabled,
144
- onClick,
145
- className: "rounded border px-1.5 py-0.5 text-xs text-muted-foreground transition hover:bg-muted hover:text-foreground disabled:opacity-40",
146
- children
147
- }
148
- );
149
- }
150
- function sortImages(images) {
151
- return [...images].sort((a, b) => {
152
- if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
153
- return (a.sortOrder ?? 0) - (b.sortOrder ?? 0);
154
- });
155
- }
156
- var STATUS_TONE = {
157
- active: "bg-emerald-100 text-emerald-800",
158
- draft: "bg-gray-100 text-gray-700",
159
- archived: "bg-orange-100 text-orange-700"
160
- };
161
- var STATUS_LABEL = {
162
- active: "Ativo",
163
- draft: "Rascunho",
164
- archived: "Arquivado"
165
- };
166
- function money(value, currency = "BRL") {
167
- const n = Number(value);
168
- if (!Number.isFinite(n)) return "\u2014";
169
- return new Intl.NumberFormat("pt-BR", { style: "currency", currency }).format(n);
170
- }
171
- function slugify(value) {
172
- return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
173
- }
174
- function productImage(getProvider) {
175
- return {
176
- get: (row) => {
177
- const images = row.images;
178
- return images?.find((i) => i.isPrimary)?.url ?? images?.[0]?.url;
179
- },
180
- upload: async (file, row) => {
181
- if (!row.id) throw new Error("Salve o produto antes de enviar a imagem.");
182
- const image = await getProvider().uploadProductImage(row.id, file);
183
- return image.url;
184
- },
185
- accept: "image/*"
186
- };
187
- }
188
- function buildShopProductEntity(currencyCode = "BRL", getProvider) {
189
- const fields = [
190
- // — Geral —
191
- {
192
- key: "name",
193
- label: "Nome do produto",
194
- type: "text",
195
- required: true,
196
- group: "general",
197
- placeholder: "Costela Bovina Defumada 400g",
198
- searchable: true,
199
- showInTable: true,
200
- sortable: true,
201
- renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
202
- row.images?.[0]?.url && /* @__PURE__ */ jsxRuntime.jsx(
203
- "img",
204
- {
205
- src: row.images[0].url,
206
- alt: "",
207
- className: "h-9 w-9 rounded object-cover",
208
- loading: "lazy"
209
- }
210
- ),
211
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
212
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-medium", children: row.name }),
213
- row.sku && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: row.sku })
214
- ] })
215
- ] })
216
- },
217
- {
218
- key: "slug",
219
- label: "URL",
220
- type: "text",
221
- group: "general",
222
- placeholder: "gerado a partir do nome",
223
- hint: "Endere\xE7o do produto na loja. Deixe em branco para gerar automaticamente.",
224
- showInTable: false
225
- },
226
- {
227
- key: "description",
228
- label: "Descri\xE7\xE3o",
229
- type: "textarea",
230
- group: "general",
231
- span: 2,
232
- placeholder: "Como \xE9 feito, como preparar, o que acompanha\u2026",
233
- showInTable: false
234
- },
235
- // — Preço —
236
- {
237
- key: "price",
238
- label: "Pre\xE7o de venda",
239
- type: "currency",
240
- required: true,
241
- group: "pricing",
242
- currency: currencyCode,
243
- currencySymbol: "R$",
244
- currencyLocale: "pt-BR",
245
- showInTable: true,
246
- sortable: true,
247
- renderCell: (v, row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right", children: [
248
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: money(v, row.currency) }),
249
- row.compareAtPrice > row.price && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-xs text-muted-foreground line-through", children: money(row.compareAtPrice, row.currency) })
250
- ] })
251
- },
252
- {
253
- key: "compareAtPrice",
254
- label: "Pre\xE7o comparativo",
255
- type: "currency",
256
- group: "pricing",
257
- currency: currencyCode,
258
- currencySymbol: "R$",
259
- currencyLocale: "pt-BR",
260
- hint: 'Pre\xE7o "de" riscado. Deixe vazio se n\xE3o estiver em promo\xE7\xE3o.',
261
- showInTable: false
262
- },
263
- {
264
- key: "discount",
265
- label: "Desconto",
266
- type: "computed",
267
- group: "pricing",
268
- showInTable: false,
269
- compute: (vals) => {
270
- const price = Number(vals.price) || 0;
271
- const compare = Number(vals.compareAtPrice) || 0;
272
- if (compare <= 0 || price <= 0 || compare <= price) return null;
273
- const pct = (compare - price) / compare * 100;
274
- return { display: `-${pct.toFixed(0)}%`, tone: "positive" };
275
- }
276
- },
277
- // — Estoque —
278
- {
279
- key: "sku",
280
- label: "SKU",
281
- type: "text",
282
- group: "inventory",
283
- placeholder: "ART-001",
284
- searchable: true,
285
- showInTable: false
286
- },
287
- {
288
- key: "inventoryCount",
289
- label: "Estoque",
290
- type: "number",
291
- group: "inventory",
292
- min: 0,
293
- placeholder: "0",
294
- hint: "Chega a zero e o produto aparece como esgotado na loja.",
295
- showInTable: true,
296
- sortable: true,
297
- renderCell: (v) => {
298
- const n = Number(v) || 0;
299
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `block text-right ${n === 0 ? "font-medium text-destructive" : n <= 3 ? "text-orange-600" : ""}`, children: n });
300
- }
301
- },
302
- // — Publicação —
303
- {
304
- key: "status",
305
- label: "Status",
306
- type: "segmented",
307
- required: true,
308
- group: "publishing",
309
- span: 2,
310
- options: [
311
- { label: "Rascunho", value: "draft", description: "S\xF3 voc\xEA v\xEA. N\xE3o aparece na loja." },
312
- { label: "Ativo", value: "active", description: "Publicado e \xE0 venda." },
313
- { label: "Arquivado", value: "archived", description: "Fora da loja, hist\xF3rico preservado." }
314
- ],
315
- showInTable: true,
316
- sortable: false,
317
- renderCell: (v) => {
318
- const s = String(v ?? "draft");
319
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${STATUS_TONE[s] ?? STATUS_TONE.draft}`, children: STATUS_LABEL[s] ?? s });
320
- }
321
- },
322
- {
323
- key: "sortOrder",
324
- label: "Ordem na vitrine",
325
- type: "number",
326
- group: "publishing",
327
- placeholder: "0",
328
- hint: "Menor aparece primeiro.",
329
- showInTable: false
330
- },
331
- // — Envio —
332
- {
333
- key: "isPhysical",
334
- label: "Produto f\xEDsico",
335
- type: "boolean",
336
- group: "shipping",
337
- hint: "Desmarque para itens digitais, que n\xE3o calculam frete.",
338
- showInTable: false
339
- },
340
- { key: "weight", label: "Peso", type: "number", group: "shipping", min: 0, placeholder: "0.4", showInTable: false },
341
- {
342
- key: "weightUnit",
343
- label: "Unidade",
344
- type: "select",
345
- group: "shipping",
346
- showInTable: false,
347
- options: [{ label: "kg", value: "kg" }, { label: "g", value: "g" }, { label: "lb", value: "lb" }]
348
- }
349
- ];
350
- return {
351
- name: "Produto",
352
- namePlural: "Produtos",
353
- icon: "Package",
354
- displayField: "name",
355
- fields,
356
- fieldGroups: [
357
- // imageSlot renders the scaffolding's image uploader alongside the group.
358
- { id: "general", label: "Informa\xE7\xF5es", columns: 2, imageSlot: true },
359
- { id: "pricing", label: "Pre\xE7o", description: 'O pre\xE7o comparativo vira o "de" riscado na vitrine.', columns: 3 },
360
- { id: "inventory", label: "Estoque", columns: 2 },
361
- { id: "publishing", label: "Publica\xE7\xE3o", description: "Controla se o produto aparece na loja.", columns: 2 },
362
- { id: "shipping", label: "Envio", columns: 3 }
363
- ],
364
- facets: [{ field: "status", allLabel: "Todos" }],
365
- image: getProvider ? productImage(getProvider) : void 0,
366
- // The form's image slot sets the COVER; this tab manages the rest of the
367
- // gallery. plg_shop_product_images has always held a collection, and until
368
- // now nothing in the admin could add a second row to it — so the storefront
369
- // carousel had exactly one slide for every product in the pool.
370
- detailTabs: getProvider ? [{
371
- id: "images",
372
- label: "Fotos",
373
- icon: "Image",
374
- component: ProductImagesTab,
375
- props: { getProvider }
376
- }] : void 0
377
- };
378
- }
379
- function money2(value) {
380
- const n = Number(value);
381
- if (!Number.isFinite(n)) return "R$ 0,00";
382
- return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(n);
383
- }
384
- function buildShopCustomerEntity() {
385
- const fields = [
386
- // — Contato —
387
- {
388
- key: "firstName",
389
- label: "Nome",
390
- type: "text",
391
- required: true,
392
- group: "contact",
393
- placeholder: "Maria",
394
- searchable: true,
395
- showInTable: true,
396
- sortable: true,
397
- renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
398
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-medium", children: [row.firstName, row.lastName].filter(Boolean).join(" ") }),
399
- row.email && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-muted-foreground", children: row.email })
400
- ] })
401
- },
402
- { key: "lastName", label: "Sobrenome", type: "text", group: "contact", placeholder: "Silva", searchable: true, showInTable: false },
403
- {
404
- key: "email",
405
- label: "E-mail",
406
- type: "text",
407
- group: "contact",
408
- placeholder: "maria@exemplo.com",
409
- searchable: true,
410
- hint: "\xC9 por ele que o pedido \xE9 vinculado ao cliente no checkout.",
411
- showInTable: false
412
- },
413
- {
414
- key: "phone",
415
- label: "Telefone",
416
- type: "text",
417
- group: "contact",
418
- placeholder: "(21) 99999-0000",
419
- searchable: true,
420
- showInTable: true,
421
- sortable: false,
422
- renderCell: (v) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: v || "\u2014" })
423
- },
424
- // — Histórico (somente leitura) —
425
- {
426
- key: "ordersCount",
427
- label: "Pedidos",
428
- type: "number",
429
- showInForm: false,
430
- showInTable: true,
431
- sortable: true,
432
- renderCell: (v) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: Number(v) || 0 })
433
- },
434
- {
435
- key: "totalSpent",
436
- label: "Total gasto",
437
- type: "number",
438
- showInForm: false,
439
- showInTable: true,
440
- sortable: true,
441
- renderCell: (v) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right font-medium", children: money2(v) })
442
- },
443
- // — Interno —
444
- {
445
- key: "notes",
446
- label: "Observa\xE7\xF5es",
447
- type: "textarea",
448
- group: "internal",
449
- span: 2,
450
- placeholder: "Prefer\xEAncias, restri\xE7\xF5es, combinados de entrega\u2026",
451
- hint: "Vis\xEDvel s\xF3 no admin. O cliente nunca v\xEA.",
452
- showInTable: false
453
- }
454
- ];
455
- return {
456
- name: "Cliente",
457
- namePlural: "Clientes",
458
- icon: "Users",
459
- displayField: "firstName",
460
- // `person` layout brings the shared archetype tabs — including Endereços,
461
- // which reads the core addresses table and therefore works in every app.
462
- layout: "person",
463
- // Without archetypeKind the shell shows every person tab, including the
464
- // staff-only Agenda (its visibleFor filter only runs when a kind is set).
465
- data: { table: "plg_shop_customers", archetypeKind: "customer" },
466
- fields,
467
- // The SAME tab and the SAME provider beauty-saas uses. Since the shop's
468
- // orders now live in public.orders with party_id set, the canonical provider
469
- // already returns them — a shop-specific adapter would be a second
470
- // implementation of "list this person's orders", which is how two apps end
471
- // up with two different answers to the same question.
472
- detailTabs: [{
473
- id: "orders",
474
- label: "Pedidos",
475
- icon: "ShoppingBag",
476
- aliases: ["appointments", "quotes"],
477
- component: saas.ClientOrdersTab,
478
- props: {
479
- provider: saas.createClientOrdersProvider(),
480
- currency: { code: "BRL", locale: "pt-BR" },
481
- // The one part that legitimately differs per app: where a document
482
- // leads. Beauty sends a booking to the agenda and an invoice to
483
- // receivables; a shop order goes to the order screen.
484
- navigator: {
485
- onNavigate: (doc) => saas.adminNavigateTo(`/shop/orders/${doc.id}`)
486
- }
487
- }
488
- }],
489
- fieldGroups: [
490
- { id: "contact", label: "Contato", columns: 2 },
491
- { id: "internal", label: "Notas internas", columns: 1 }
492
- ]
493
- };
494
- }
495
- var STATUS_TONE2 = {
496
- active: "bg-emerald-100 text-emerald-800",
497
- draft: "bg-gray-100 text-gray-700",
498
- expired: "bg-orange-100 text-orange-700",
499
- disabled: "bg-red-100 text-red-700"
500
- };
501
- function buildShopDiscountEntity() {
502
- const fields = [
503
- // — Cupom —
504
- {
505
- key: "title",
506
- label: "Nome interno",
507
- type: "text",
508
- required: true,
509
- group: "coupon",
510
- placeholder: "Bem-vindo 10%",
511
- hint: "S\xF3 o admin v\xEA. Serve para voc\xEA identificar a campanha.",
512
- searchable: true,
513
- showInTable: true,
514
- sortable: true,
515
- renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
516
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-medium", children: row.title }),
517
- row.code && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono text-xs text-muted-foreground", children: row.code })
518
- ] })
519
- },
520
- {
521
- key: "code",
522
- label: "C\xF3digo",
523
- type: "text",
524
- group: "coupon",
525
- placeholder: "ARTORIUS10",
526
- hint: "O que o cliente digita no carrinho. Mai\xFAsculas e min\xFAsculas n\xE3o importam.",
527
- searchable: true,
528
- showInTable: false
529
- },
530
- // — Regra —
531
- {
532
- key: "type",
533
- label: "Tipo",
534
- type: "segmented",
535
- required: true,
536
- group: "rule",
537
- span: 2,
538
- options: [
539
- { label: "Percentual", value: "percentage", description: "Desconta uma % do subtotal." },
540
- { label: "Valor fixo", value: "fixed_amount", description: "Desconta um valor em reais." },
541
- { label: "Frete gr\xE1tis", value: "free_shipping", description: "Zera o frete do pedido." }
542
- ],
543
- showInTable: true,
544
- sortable: false,
545
- renderCell: (v) => {
546
- const labels = {
547
- percentage: "Percentual",
548
- fixed_amount: "Valor fixo",
549
- free_shipping: "Frete gr\xE1tis",
550
- buy_x_get_y: "Leve X pague Y"
551
- };
552
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: labels[String(v)] ?? String(v) });
553
- }
554
- },
555
- {
556
- key: "value",
557
- label: "Valor",
558
- type: "number",
559
- group: "rule",
560
- min: 0,
561
- placeholder: "10",
562
- hint: "Percentual: 10 = 10%. Valor fixo: 10 = R$ 10,00. Frete gr\xE1tis ignora este campo.",
563
- showInTable: true,
564
- sortable: true,
565
- renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right font-medium", children: row.type === "percentage" ? `${row.value}%` : row.type === "free_shipping" ? "\u2014" : new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(row.value) || 0) })
566
- },
567
- // — Limites —
568
- {
569
- key: "usageLimit",
570
- label: "Limite de usos",
571
- type: "number",
572
- group: "limits",
573
- min: 0,
574
- placeholder: "sem limite",
575
- hint: "Deixe vazio para uso ilimitado.",
576
- showInTable: false
577
- },
578
- {
579
- key: "oncePerCustomer",
580
- label: "Uma vez por cliente",
581
- type: "boolean",
582
- group: "limits",
583
- hint: "S\xF3 vale para quem se identifica no checkout \u2014 compra como visitante n\xE3o \xE9 rastreada.",
584
- showInTable: false
585
- },
586
- {
587
- key: "timesUsed",
588
- label: "Usos",
589
- type: "number",
590
- showInForm: false,
591
- showInTable: true,
592
- sortable: true,
593
- renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "block text-right text-muted-foreground", children: [
594
- row.timesUsed,
595
- row.usageLimit ? ` / ${row.usageLimit}` : ""
596
- ] })
597
- },
598
- // — Vigência —
599
- { key: "startsAt", label: "In\xEDcio", type: "date", group: "schedule", showInTable: false },
600
- {
601
- key: "endsAt",
602
- label: "Fim",
603
- type: "date",
604
- group: "schedule",
605
- hint: "Vazio = sem data de expira\xE7\xE3o.",
606
- showInTable: false
607
- },
608
- {
609
- key: "status",
610
- label: "Status",
611
- type: "segmented",
612
- required: true,
613
- group: "schedule",
614
- span: 2,
615
- options: [
616
- { label: "Rascunho", value: "draft", description: "N\xE3o aceito no carrinho." },
617
- { label: "Ativo", value: "active", description: "Valendo agora." },
618
- { label: "Desativado", value: "disabled", description: "Suspenso sem apagar o hist\xF3rico." }
619
- ],
620
- showInTable: true,
621
- sortable: false,
622
- renderCell: (v) => {
623
- const s = String(v ?? "draft");
624
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${STATUS_TONE2[s] ?? STATUS_TONE2.draft}`, children: s });
625
- }
626
- }
627
- ];
628
- return {
629
- name: "Desconto",
630
- namePlural: "Descontos",
631
- icon: "Tag",
632
- displayField: "title",
633
- fields,
634
- fieldGroups: [
635
- { id: "coupon", label: "Cupom", columns: 2 },
636
- { id: "rule", label: "Regra", description: "O desconto \xE9 recalculado no servidor a cada pedido.", columns: 2 },
637
- { id: "limits", label: "Limites de uso", columns: 2 },
638
- { id: "schedule", label: "Vig\xEAncia", columns: 2 }
639
- ],
640
- facets: [{ field: "status", allLabel: "Todos" }]
641
- };
642
- }
643
- var cep = (value) => {
644
- const digits = String(value ?? "").replace(/\D/g, "").padStart(8, "0");
645
- return `${digits.slice(0, 5)}-${digits.slice(5)}`;
646
- };
647
- function buildShopShippingZoneEntity(currency = "BRL") {
648
- const money3 = (value) => value == null ? "\u2014" : new Intl.NumberFormat("pt-BR", { style: "currency", currency }).format(Number(value));
649
- const fields = [
650
- {
651
- key: "name",
652
- label: "Nome da zona",
653
- type: "text",
654
- required: true,
655
- group: "zone",
656
- placeholder: "Rio de Janeiro \u2014 capital",
657
- hint: "Aparece para o cliente na hora de escolher a entrega.",
658
- searchable: true,
659
- showInTable: true,
660
- sortable: true
661
- },
662
- {
663
- key: "carrier",
664
- label: "Transportadora",
665
- type: "text",
666
- group: "zone",
667
- placeholder: "Correios, motoboy pr\xF3prio\u2026",
668
- hint: "Opcional. Mostrado antes do nome da zona quando preenchido.",
669
- showInTable: true
670
- },
671
- {
672
- key: "postalFrom",
673
- label: "CEP inicial",
674
- type: "text",
675
- required: true,
676
- group: "zone",
677
- placeholder: "20000-000",
678
- hint: "In\xEDcio da faixa. Pode digitar com ou sem tra\xE7o.",
679
- showInTable: true,
680
- renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "whitespace-nowrap font-mono text-xs", children: [
681
- cep(row.postalFrom),
682
- " \u2192 ",
683
- cep(row.postalTo)
684
- ] })
685
- },
686
- {
687
- key: "postalTo",
688
- label: "CEP final",
689
- type: "text",
690
- required: true,
691
- group: "zone",
692
- placeholder: "23799-999",
693
- hint: "Fim da faixa, inclusive.",
694
- showInTable: false
695
- },
696
- {
697
- key: "rate",
698
- label: "Valor do frete",
699
- type: "number",
700
- required: true,
701
- group: "pricing",
702
- hint: "Cobrado nos pedidos entregues nessa faixa.",
703
- showInTable: true,
704
- sortable: true,
705
- renderCell: (value) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: money3(value) })
706
- },
707
- {
708
- key: "freeAbove",
709
- label: "Frete gr\xE1tis acima de",
710
- type: "number",
711
- group: "pricing",
712
- hint: "Deixe vazio para nunca dar frete gr\xE1tis nessa zona. Vale mais que a regra geral da loja.",
713
- showInTable: true,
714
- renderCell: (value) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: money3(value) })
715
- },
716
- {
717
- key: "etaMinDays",
718
- label: "Prazo m\xEDnimo (dias)",
719
- type: "number",
720
- group: "delivery",
721
- hint: "0 = no mesmo dia.",
722
- showInTable: false
723
- },
724
- {
725
- key: "etaMaxDays",
726
- label: "Prazo m\xE1ximo (dias)",
727
- type: "number",
728
- group: "delivery",
729
- hint: 'O cliente v\xEA a faixa "de X a Y dias \xFAteis" na p\xE1gina do produto.',
730
- showInTable: true,
731
- renderCell: (_v, row) => row.etaMinDays == null && row.etaMaxDays == null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "\u2014" }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
732
- row.etaMinDays ?? 0,
733
- "\u2013",
734
- row.etaMaxDays ?? row.etaMinDays,
735
- " dias"
736
- ] })
737
- },
738
- {
739
- key: "active",
740
- label: "Ativa",
741
- type: "boolean",
742
- group: "delivery",
743
- hint: "Desativar para de cotar essa faixa sem apagar a configura\xE7\xE3o.",
744
- showInTable: true,
745
- renderCell: (value) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${value ? "bg-emerald-100 text-emerald-800" : "bg-gray-100 text-gray-700"}`, children: value ? "Ativa" : "Inativa" })
746
- },
747
- {
748
- key: "sortOrder",
749
- label: "Ordem",
750
- type: "number",
751
- group: "delivery",
752
- hint: "Desempate quando duas zonas custam o mesmo. Menor aparece primeiro.",
753
- showInTable: false
754
- }
755
- ];
756
- return {
757
- name: "Zona de entrega",
758
- namePlural: "Entrega",
759
- icon: "Truck",
760
- displayField: "name",
761
- fields,
762
- fieldGroups: [
763
- {
764
- id: "zone",
765
- label: "Faixa de CEP",
766
- columns: 2,
767
- description: "Faixas podem se sobrepor: quando duas cobrem o mesmo CEP, o cliente paga a mais barata."
768
- },
769
- {
770
- id: "pricing",
771
- label: "Pre\xE7o",
772
- columns: 2,
773
- description: "O frete cobrado \xE9 recalculado no servidor a cada pedido, a partir daqui."
774
- },
775
- { id: "delivery", label: "Prazo e status", columns: 2 }
776
- ],
777
- facets: [{ field: "active", allLabel: "Todas" }]
778
- };
779
- }
780
-
781
- // src/data/shared.ts
782
- var resolve = (p) => typeof p === "function" ? p() : p;
783
- function applyQuery(rows, query, facetKey) {
784
- const prop = (row, key) => row[key];
785
- let data = rows;
786
- if (facetKey) {
787
- const wanted = query.filters?.[facetKey];
788
- if (wanted && wanted !== "all") data = data.filter((row) => prop(row, facetKey) === wanted);
789
- }
790
- if (query.sortBy) {
791
- const dir = query.sortDir === "desc" ? -1 : 1;
792
- const key = query.sortBy;
793
- data = [...data].sort((a, b) => {
794
- const av = prop(a, key);
795
- const bv = prop(b, key);
796
- if (typeof av === "number" && typeof bv === "number") return (av - bv) * dir;
797
- return String(av ?? "").localeCompare(String(bv ?? ""), "pt-BR") * dir;
798
- });
799
- }
800
- const total = data.length;
801
- const page = query.page ?? 1;
802
- const size = query.pageSize ?? total;
803
- return { data: data.slice((page - 1) * size, page * size), total };
804
- }
805
-
806
- // src/data/productDataProvider.ts
807
- function createShopProductDataProvider(provider) {
808
- return {
809
- async list(query) {
810
- const rows = await resolve(provider).listProducts(
811
- query.search ? { search: query.search } : void 0
812
- );
813
- return applyQuery(rows, query, "status");
814
- },
815
- async create(data) {
816
- const input = data;
817
- const slug = input.slug?.trim() || slugify(String(input.name ?? ""));
818
- return resolve(provider).createProduct({
819
- ...input,
820
- slug
821
- });
822
- },
823
- async update(id, data) {
824
- const patch = { ...data };
825
- if (patch.slug !== void 0 && !String(patch.slug).trim()) {
826
- patch.slug = slugify(String(patch.name ?? ""));
827
- }
828
- return resolve(provider).updateProduct(id, patch);
829
- },
830
- async remove(id) {
831
- await resolve(provider).deleteProduct(id);
832
- }
833
- };
834
- }
835
-
836
- // src/data/customerDataProvider.ts
837
- function createShopCustomerDataProvider(provider) {
838
- return {
839
- async list(query) {
840
- const rows = await resolve(provider).listCustomers(
841
- query.search ? { search: query.search } : void 0
842
- );
843
- return applyQuery(rows, query);
844
- },
845
- async create(data) {
846
- return resolve(provider).createCustomer(data);
847
- },
848
- async update(id, data) {
849
- const { ordersCount: _o, totalSpent: _t, ...patch } = data;
850
- return resolve(provider).updateCustomer(id, patch);
851
- },
852
- async remove(id) {
853
- await resolve(provider).deleteCustomer(id);
854
- }
855
- };
856
- }
857
-
858
- // src/data/discountDataProvider.ts
859
- function createShopDiscountDataProvider(provider) {
860
- return {
861
- async list(query) {
862
- const rows = await resolve(provider).listDiscounts();
863
- const term = query.search?.trim().toLowerCase();
864
- const filtered = term ? rows.filter((d) => d.title.toLowerCase().includes(term) || (d.code ?? "").toLowerCase().includes(term)) : rows;
865
- return applyQuery(filtered, query, "status");
866
- },
867
- async create(data) {
868
- const input = data;
869
- return resolve(provider).createDiscount({
870
- ...input,
871
- // Codes are matched case-insensitively server-side; store them upper so
872
- // the admin list reads consistently.
873
- code: input.code ? String(input.code).trim().toUpperCase() : void 0
874
- });
875
- },
876
- async update(id, data) {
877
- const { timesUsed: _used, ...patch } = data;
878
- if (patch.code !== void 0) {
879
- patch.code = patch.code ? String(patch.code).trim().toUpperCase() : null;
880
- }
881
- return resolve(provider).updateDiscount(id, patch);
882
- },
883
- async remove(id) {
884
- await resolve(provider).deleteDiscount(id);
885
- }
886
- };
887
- }
888
-
889
- // src/data/shippingZoneDataProvider.ts
890
- function createShopShippingZoneDataProvider(provider) {
891
- const shop = () => {
892
- const p = resolve(provider);
893
- if (!p.listShippingZones) {
894
- throw new Error("Este provider de loja n\xE3o suporta zonas de entrega.");
895
- }
896
- return p;
897
- };
898
- return {
899
- async list(query) {
900
- const rows = await shop().listShippingZones();
901
- const term = query.search?.trim().toLowerCase();
902
- const filtered = term ? rows.filter((z) => z.name.toLowerCase().includes(term) || (z.carrier ?? "").toLowerCase().includes(term)) : rows;
903
- return applyQuery(filtered, query);
904
- },
905
- async create(data) {
906
- return shop().createShippingZone(data);
907
- },
908
- async update(id, data) {
909
- const { id: _id, tenantId: _tenant, ...patch } = data;
910
- return shop().updateShippingZone(id, patch);
911
- },
912
- async remove(id) {
913
- await shop().deleteShippingZone(id);
914
- }
915
- };
916
- }
917
- var CURRENCIES = [
918
- { value: "BRL", label: "Real (R$)" },
919
- { value: "USD", label: "US Dollar ($)" },
920
- { value: "EUR", label: "Euro (\u20AC)" },
921
- { value: "GBP", label: "British Pound (\xA3)" }
922
- ];
923
- var LOCALES = [
924
- { value: "pt-BR", label: "Portugu\xEAs (Brasil)" },
925
- { value: "en-US", label: "English (US)" },
926
- { value: "es-ES", label: "Espa\xF1ol" }
927
- ];
928
- function ShopSettings({ currency }) {
929
- const [code, setCode] = React2__default.default.useState(currency?.code ?? "BRL");
930
- const [locale, setLocale] = React2__default.default.useState(currency?.locale ?? "pt-BR");
931
- const [showOutOfStock, setShowOutOfStock] = React2__default.default.useState(true);
932
- const [trackInventory, setTrackInventory] = React2__default.default.useState(true);
933
- const [requireSku, setRequireSku] = React2__default.default.useState(false);
934
- const [guestCheckout, setGuestCheckout] = React2__default.default.useState(true);
935
- const [enableDiscounts, setEnableDiscounts] = React2__default.default.useState(true);
936
- const [orderEmails, setOrderEmails] = React2__default.default.useState(true);
937
- const [lowStockAlerts, setLowStockAlerts] = React2__default.default.useState(false);
938
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
939
- /* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: "Loja", description: "Moeda e formata\xE7\xE3o da vitrine.", children: [
940
- /* @__PURE__ */ jsxRuntime.jsx(
941
- saas.SelectRow,
942
- {
943
- label: "Moeda",
944
- description: "Moeda padr\xE3o para pre\xE7os e checkout.",
945
- value: code,
946
- options: CURRENCIES,
947
- onChange: setCode
948
- }
949
- ),
950
- /* @__PURE__ */ jsxRuntime.jsx(
951
- saas.SelectRow,
952
- {
953
- label: "Idioma / formato",
954
- description: "Formata\xE7\xE3o de n\xFAmeros, pre\xE7os e datas.",
955
- value: locale,
956
- options: LOCALES,
957
- onChange: setLocale
958
- }
959
- )
960
- ] }),
961
- /* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: "Cat\xE1logo", description: "Como os produtos aparecem e s\xE3o gerenciados.", children: [
962
- /* @__PURE__ */ jsxRuntime.jsx(
963
- saas.ToggleRow,
964
- {
965
- label: "Mostrar produtos esgotados",
966
- description: "Exibir itens sem estoque na vitrine.",
967
- checked: showOutOfStock,
968
- onChange: setShowOutOfStock
969
- }
970
- ),
971
- /* @__PURE__ */ jsxRuntime.jsx(
972
- saas.ToggleRow,
973
- {
974
- label: "Controlar estoque",
975
- description: "Acompanhar quantidade e dar baixa no checkout.",
976
- checked: trackInventory,
977
- onChange: setTrackInventory
978
- }
979
- ),
980
- /* @__PURE__ */ jsxRuntime.jsx(
981
- saas.ToggleRow,
982
- {
983
- label: "Exigir SKU",
984
- description: "Tornar o SKU obrigat\xF3rio ao criar produtos.",
985
- checked: requireSku,
986
- onChange: setRequireSku
987
- }
988
- )
989
- ] }),
990
- /* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: "Checkout", description: "Op\xE7\xF5es de compra e pagamento.", children: [
991
- /* @__PURE__ */ jsxRuntime.jsx(
992
- saas.ToggleRow,
993
- {
994
- label: "Checkout sem cadastro",
995
- description: "Permitir compra como convidado.",
996
- checked: guestCheckout,
997
- onChange: setGuestCheckout
998
- }
999
- ),
1000
- /* @__PURE__ */ jsxRuntime.jsx(
1001
- saas.ToggleRow,
1002
- {
1003
- label: "Cupons de desconto",
1004
- description: "Habilitar c\xF3digos de desconto no carrinho.",
1005
- checked: enableDiscounts,
1006
- onChange: setEnableDiscounts
1007
- }
1008
- )
1009
- ] }),
1010
- /* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: "Notifica\xE7\xF5es", description: "Avisos por e-mail da loja.", children: [
1011
- /* @__PURE__ */ jsxRuntime.jsx(
1012
- saas.ToggleRow,
1013
- {
1014
- label: "E-mail de novo pedido",
1015
- description: "Notificar a equipe a cada novo pedido.",
1016
- checked: orderEmails,
1017
- onChange: setOrderEmails
1018
- }
1019
- ),
1020
- /* @__PURE__ */ jsxRuntime.jsx(
1021
- saas.ToggleRow,
1022
- {
1023
- label: "Alerta de estoque baixo",
1024
- description: "Avisar quando produtos atingirem o m\xEDnimo.",
1025
- checked: lowStockAlerts,
1026
- onChange: setLowStockAlerts
1027
- }
1028
- )
1029
- ] })
1030
- ] });
1031
- }
1032
-
1033
- // src/index.ts
1034
- var ShopPage = React2__default.default.lazy(() => import('./ShopPage-ALBPZAEW.cjs').then((m) => ({ default: m.ShopPage })));
1035
- function createShopPlugin(options) {
1036
- const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
1037
- shop.setShopTenantResolver(() => saas.useOrganizationStore.getState().currentOrg?.id);
1038
- saas.registerEntityPath("shop_customer", "/shop/customers");
1039
- const pageFor = (section) => {
1040
- const Wrapped = () => React2__default.default.createElement(
1041
- React2__default.default.Suspense,
1042
- { fallback: null },
1043
- React2__default.default.createElement(
1044
- ShopPage,
1045
- { provider: options?.provider, section }
1046
- )
1047
- );
1048
- Wrapped.displayName = section ? `ShopPage:${section}` : "ShopPageWrapper";
1049
- return Wrapped;
1050
- };
1051
- const resolveProvider = () => typeof options?.provider === "function" ? options.provider() : options?.provider ?? shop.getShopProvider();
1052
- const ProductsCrudPage = saas.createCrudPage(buildShopProductEntity(curr.code, resolveProvider), {
1053
- dataProvider: createShopProductDataProvider(resolveProvider),
1054
- feature: "shop"
1055
- });
1056
- const CustomersCrudPage = saas.createCrudPage(buildShopCustomerEntity(), {
1057
- dataProvider: createShopCustomerDataProvider(resolveProvider),
1058
- feature: "shop"
1059
- });
1060
- const DiscountsCrudPage = saas.createCrudPage(buildShopDiscountEntity(), {
1061
- dataProvider: createShopDiscountDataProvider(resolveProvider),
1062
- feature: "shop"
1063
- });
1064
- const ShippingZonesCrudPage = saas.createCrudPage(buildShopShippingZoneEntity(curr.code), {
1065
- dataProvider: createShopShippingZoneDataProvider(resolveProvider),
1066
- feature: "shop"
1067
- });
1068
- const CRUD_PAGES = {
1069
- products: ProductsCrudPage,
1070
- customers: CustomersCrudPage,
1071
- discounts: DiscountsCrudPage,
1072
- shipping: ShippingZonesCrudPage
1073
- };
1074
- const SECTIONS = [
1075
- { id: "products", label: options?.sectionLabels?.products ?? "Produtos", icon: "Package" },
1076
- { id: "orders", label: options?.sectionLabels?.orders ?? "Pedidos", icon: "ShoppingCart" },
1077
- { id: "customers", label: options?.sectionLabels?.customers ?? "Clientes", icon: "Users" },
1078
- { id: "discounts", label: options?.sectionLabels?.discounts ?? "Descontos", icon: "Tag" },
1079
- { id: "shipping", label: options?.sectionLabels?.shipping ?? "Entrega", icon: "Truck" }
1080
- ];
1081
- const navSection = options?.navSection ?? "main";
1082
- const navPosition = options?.navPosition ?? 6;
1083
- return {
1084
- id: "shop",
1085
- name: options?.navLabel ?? "Shop",
1086
- icon: "ShoppingBag",
1087
- version: "1.0.0",
1088
- scope: options?.scope ?? "vertical",
1089
- verticalId: options?.verticalId,
1090
- // Storefront/commerce-operator-console surface — targets the ecommerce
1091
- // Panel world, not a saas admin app.
1092
- scaffolds: ["ecommerce"],
1093
- defaultEnabled: true,
1094
- dependencies: [],
1095
- navigation: [
1096
- {
1097
- section: navSection,
1098
- position: navPosition,
1099
- label: options?.navLabel ?? "Shop",
1100
- route: "/shop",
1101
- icon: "ShoppingBag",
1102
- permission: { feature: "shop", action: "read" },
1103
- // "Loja" and "Produtos" are the same screen — don't list both.
1104
- indexChild: false
1105
- },
1106
- ...SECTIONS.map((s, i) => ({
1107
- section: navSection,
1108
- position: navPosition + i + 1,
1109
- label: s.label,
1110
- route: `/shop/${s.id}`,
1111
- icon: s.icon,
1112
- permission: { feature: "shop", action: "read" }
1113
- }))
1114
- ],
1115
- routes: [
1116
- {
1117
- // Kept so existing links to /shop still resolve; renders the tabbed page.
1118
- path: "/shop",
1119
- component: pageFor(),
1120
- permission: { feature: "shop", action: "read" }
1121
- },
1122
- ...SECTIONS.map((s) => ({
1123
- path: `/shop/${s.id}`,
1124
- component: CRUD_PAGES[s.id] ?? pageFor(s.id),
1125
- permission: { feature: "shop", action: "read" }
1126
- }))
1127
- ],
1128
- widgets: [],
1129
- aiTools: [
1130
- {
1131
- id: "shop.list-products",
1132
- name: "listProducts",
1133
- description: "Lists products in the shop catalog.",
1134
- icon: "Package",
1135
- mode: "read",
1136
- category: "Shop",
1137
- parameters: {
1138
- type: "object",
1139
- properties: {
1140
- search: { type: "string", description: "Filter by name" },
1141
- status: { type: "string", enum: ["draft", "active", "archived"] }
1142
- }
1143
- },
1144
- suggestions: [
1145
- { label: "What products are available?" },
1146
- { label: "Which products are out of stock?" }
1147
- ]
1148
- },
1149
- {
1150
- id: "shop.list-orders",
1151
- name: "listOrders",
1152
- description: "Lists recent shop orders.",
1153
- icon: "ShoppingCart",
1154
- mode: "read",
1155
- category: "Shop",
1156
- parameters: {
1157
- type: "object",
1158
- properties: {
1159
- financialStatus: { type: "string", enum: ["pending", "paid", "refunded", "voided"] },
1160
- fulfillmentStatus: { type: "string", enum: ["unfulfilled", "partially_fulfilled", "fulfilled"] }
1161
- }
1162
- },
1163
- suggestions: [
1164
- { label: "Show me pending orders" },
1165
- { label: "How many orders were paid today?" }
1166
- ]
1167
- }
1168
- ],
1169
- settings: [
1170
- {
1171
- id: "shop",
1172
- label: "Shop",
1173
- icon: "ShoppingBag",
1174
- component: (() => {
1175
- const ShopSettingsTab = () => React2__default.default.createElement(ShopSettings, { currency: curr });
1176
- ShopSettingsTab.displayName = "ShopSettingsTab";
1177
- return ShopSettingsTab;
1178
- })(),
1179
- order: 30,
1180
- permission: { feature: "shop", action: "read" }
1181
- }
1182
- ]
1183
- };
1184
- }
1185
-
1186
- exports.createShopPlugin = createShopPlugin;
1187
- //# sourceMappingURL=index.cjs.map
1188
- //# sourceMappingURL=index.cjs.map