@fayz-ai/plugin-shop 0.24.4 → 0.24.6

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 (61) hide show
  1. package/dist/CheckoutSettings-GQRYZF6I.js +128 -0
  2. package/dist/CheckoutSettings-GQRYZF6I.js.map +1 -0
  3. package/dist/ContentPage-2PHOZSFZ.js +510 -0
  4. package/dist/ContentPage-2PHOZSFZ.js.map +1 -0
  5. package/dist/DeliveryStoreSettings-L2RXOVB6.js +172 -0
  6. package/dist/DeliveryStoreSettings-L2RXOVB6.js.map +1 -0
  7. package/dist/FulfillmentSettings-PW233UOK.js +61 -0
  8. package/dist/FulfillmentSettings-PW233UOK.js.map +1 -0
  9. package/dist/MarketsSettings-YUEVDLIR.js +19 -0
  10. package/dist/MarketsSettings-YUEVDLIR.js.map +1 -0
  11. package/dist/PaymentsSettings-5CI74TY2.js +318 -0
  12. package/dist/PaymentsSettings-5CI74TY2.js.map +1 -0
  13. package/dist/ProductContentTab-PSNDS7JZ.js +4 -0
  14. package/dist/ProductContentTab-PSNDS7JZ.js.map +1 -0
  15. package/dist/ProductListingTab-55Y5RXU7.js +4 -0
  16. package/dist/ProductListingTab-55Y5RXU7.js.map +1 -0
  17. package/dist/ProductMarketPricesTab-QD2ZXEWL.js +4 -0
  18. package/dist/ProductMarketPricesTab-QD2ZXEWL.js.map +1 -0
  19. package/dist/ShippingSettings-UBTNRBJ4.js +9 -0
  20. package/dist/ShippingSettings-UBTNRBJ4.js.map +1 -0
  21. package/dist/{ShopPage-NUIIWBCB.js → ShopPage-NARKHXOO.js} +6 -3
  22. package/dist/ShopPage-NARKHXOO.js.map +1 -0
  23. package/dist/StoreGeneralSettings-DEZ2CMVG.js +97 -0
  24. package/dist/StoreGeneralSettings-DEZ2CMVG.js.map +1 -0
  25. package/dist/chunk-2MXOKJQO.js +23 -0
  26. package/dist/chunk-2MXOKJQO.js.map +1 -0
  27. package/dist/chunk-4JM32Z7J.js +51 -0
  28. package/dist/chunk-4JM32Z7J.js.map +1 -0
  29. package/dist/chunk-53Y6VLAW.js +132 -0
  30. package/dist/chunk-53Y6VLAW.js.map +1 -0
  31. package/dist/chunk-6V2VAUWP.js +24 -0
  32. package/dist/chunk-6V2VAUWP.js.map +1 -0
  33. package/dist/chunk-FQQMEM26.js +59 -0
  34. package/dist/chunk-FQQMEM26.js.map +1 -0
  35. package/dist/chunk-JYT6BTRE.js +298 -0
  36. package/dist/chunk-JYT6BTRE.js.map +1 -0
  37. package/dist/chunk-MFEOIJI7.js +127 -0
  38. package/dist/chunk-MFEOIJI7.js.map +1 -0
  39. package/dist/chunk-R5CUDFOI.js +61 -0
  40. package/dist/chunk-R5CUDFOI.js.map +1 -0
  41. package/dist/chunk-STHGZWBQ.js +1455 -0
  42. package/dist/chunk-STHGZWBQ.js.map +1 -0
  43. package/dist/{chunk-XV3KZRQX.js → chunk-STZYH32S.js} +9 -140
  44. package/dist/chunk-STZYH32S.js.map +1 -0
  45. package/dist/chunk-TDV3SFXT.js +75 -0
  46. package/dist/chunk-TDV3SFXT.js.map +1 -0
  47. package/dist/chunk-YUG6XQHJ.js +55 -0
  48. package/dist/chunk-YUG6XQHJ.js.map +1 -0
  49. package/dist/data/registry-routes.d.ts.map +1 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +129 -3456
  52. package/dist/index.js.map +1 -1
  53. package/dist/settings/ShippingSettings.d.ts.map +1 -1
  54. package/dist/views/EmailTemplatePage.d.ts.map +1 -1
  55. package/dist/views/EmailsPage.d.ts.map +1 -1
  56. package/dist/views/ProductContentTab.d.ts.map +1 -1
  57. package/dist/views/ProductMarketPricesTab.d.ts.map +1 -1
  58. package/dist/views/media-links.d.ts.map +1 -1
  59. package/package.json +7 -7
  60. package/dist/ShopPage-NUIIWBCB.js.map +0 -1
  61. package/dist/chunk-XV3KZRQX.js.map +0 -1
@@ -0,0 +1,298 @@
1
+ import { errorMessage } from './chunk-2MXOKJQO.js';
2
+ import { useState, useRef, useEffect } from 'react';
3
+ import { Button, Skeleton } from '@fayz-ai/ui';
4
+ import { DetailTabHeader, AskAIButton } from '@fayz-ai/admin';
5
+ import { Plus, UploadCloud } from 'lucide-react';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+ import { appHref } from '@fayz-ai/core';
8
+
9
+ function ProductImagesTab({ item, getProvider, footer }) {
10
+ const product = item;
11
+ const [images, setImages] = useState(null);
12
+ const [dragging, setDragging] = useState(false);
13
+ const [busy, setBusy] = useState(false);
14
+ const [error, setError] = useState(null);
15
+ const fileRef = useRef(null);
16
+ async function reload() {
17
+ const fresh = await getProvider().getProduct(product.id);
18
+ setImages(sortImages(fresh?.images ?? []));
19
+ }
20
+ useEffect(() => {
21
+ setImages(null);
22
+ void reload().catch((err) => {
23
+ setImages([]);
24
+ setError(errorMessage(err));
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(errorMessage(err));
35
+ } finally {
36
+ setBusy(false);
37
+ }
38
+ }
39
+ async function upload(picked) {
40
+ const files = Array.from(picked ?? []).filter((file) => file.type.startsWith("image/"));
41
+ if (!files.length) return;
42
+ await run(async () => {
43
+ for (const file of files) {
44
+ await getProvider().uploadProductImage(product.id, file);
45
+ }
46
+ if (fileRef.current) fileRef.current.value = "";
47
+ });
48
+ }
49
+ function move(index, delta) {
50
+ const target = index + delta;
51
+ if (!images || target < 0 || target >= images.length) return;
52
+ const a = images[index];
53
+ const b = images[target];
54
+ void run(async () => {
55
+ const update2 = getProvider().updateProductImage;
56
+ if (!update2) throw new Error("Este provider n\xE3o suporta reordenar imagens.");
57
+ await update2(a.id, { sortOrder: b.sortOrder ?? target });
58
+ await update2(b.id, { sortOrder: a.sortOrder ?? index });
59
+ });
60
+ }
61
+ const update = getProvider().updateProductImage;
62
+ return (
63
+ // The whole tab takes a drop: aiming at a small box with six files in hand
64
+ // is the part of uploading that goes wrong.
65
+ /* @__PURE__ */ jsxs(
66
+ "div",
67
+ {
68
+ className: "space-y-4",
69
+ onDragOver: (event) => {
70
+ if (event.dataTransfer.types.includes("Files")) {
71
+ event.preventDefault();
72
+ setDragging(true);
73
+ }
74
+ },
75
+ onDragLeave: (event) => {
76
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
77
+ },
78
+ onDrop: (event) => {
79
+ if (!event.dataTransfer.files.length) return;
80
+ event.preventDefault();
81
+ setDragging(false);
82
+ void upload(event.dataTransfer.files);
83
+ },
84
+ children: [
85
+ /* @__PURE__ */ jsx(
86
+ DetailTabHeader,
87
+ {
88
+ title: "Fotos do produto",
89
+ description: "A capa \xE9 a foto do produto em todo lugar: no cadastro, no estoque, no cat\xE1logo, no carrinho e no pedido. As demais viram o carrossel na p\xE1gina do produto.",
90
+ actions: (
91
+ // The same Button as every other tab. It used to be a <label> hand-
92
+ // painted to look like one, which drifted the moment the real button
93
+ // changed — different height, no icon, no disabled state.
94
+ /* @__PURE__ */ jsxs(Button, { disabled: busy, onClick: () => fileRef.current?.click(), children: [
95
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
96
+ busy ? "Enviando\u2026" : "Adicionar fotos"
97
+ ] })
98
+ )
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsx(
102
+ "input",
103
+ {
104
+ ref: fileRef,
105
+ type: "file",
106
+ accept: "image/*",
107
+ multiple: true,
108
+ disabled: busy,
109
+ className: "hidden",
110
+ onChange: (event) => void upload(event.target.files)
111
+ }
112
+ ),
113
+ error && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error }),
114
+ images === null ? /* @__PURE__ */ jsx("ul", { className: "fz-cards gap-3", "aria-busy": "true", children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
115
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-square w-full rounded-none" }),
116
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 py-2", children: [
117
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-14" }),
118
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
119
+ ] })
120
+ ] }, i)) }) : images.length === 0 || dragging ? /* @__PURE__ */ jsxs(
121
+ Button,
122
+ {
123
+ variant: "bare",
124
+ size: "bare",
125
+ type: "button",
126
+ disabled: busy,
127
+ onClick: () => fileRef.current?.click(),
128
+ className: `font-normal whitespace-normal flex w-full flex-col items-center gap-2 rounded-lg border border-dashed px-4 py-10 text-center text-sm transition-colors ${dragging ? "border-primary bg-primary/5 text-foreground" : "text-muted-foreground hover:bg-muted/40"}`,
129
+ children: [
130
+ /* @__PURE__ */ jsx(UploadCloud, { className: "h-6 w-6", "aria-hidden": true }),
131
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: dragging ? "Solte para enviar" : "Arraste as fotos para c\xE1, ou clique para escolher" }),
132
+ !dragging && /* @__PURE__ */ jsx("span", { className: "max-w-md text-xs", children: "Produtos com mais de uma foto vendem melhor \u2014 mostre a embalagem, o produto pronto e uma refer\xEAncia de tamanho." })
133
+ ]
134
+ }
135
+ ) : /* @__PURE__ */ jsx("ul", { className: "fz-cards gap-3", children: images.map((image, index) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
136
+ /* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted", children: [
137
+ /* @__PURE__ */ jsx("img", { src: image.url, alt: image.altText ?? "", className: "h-full w-full object-cover" }),
138
+ image.isPrimary && /* @__PURE__ */ 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" })
139
+ ] }),
140
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-1 gap-y-1.5 px-2 py-1.5", children: [
141
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
142
+ /* @__PURE__ */ jsx(
143
+ IconButton,
144
+ {
145
+ label: "Mover para tr\xE1s",
146
+ disabled: busy || index === 0,
147
+ onClick: () => move(index, -1),
148
+ children: "\u2190"
149
+ }
150
+ ),
151
+ /* @__PURE__ */ jsx(
152
+ IconButton,
153
+ {
154
+ label: "Mover para frente",
155
+ disabled: busy || index === images.length - 1,
156
+ onClick: () => move(index, 1),
157
+ children: "\u2192"
158
+ }
159
+ )
160
+ ] }),
161
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
162
+ !image.isPrimary && update && /* @__PURE__ */ jsx(
163
+ Button,
164
+ {
165
+ variant: "bare",
166
+ size: "bare",
167
+ type: "button",
168
+ disabled: busy,
169
+ onClick: () => void run(() => update(image.id, { isPrimary: true })),
170
+ className: "font-normal whitespace-nowrap rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50",
171
+ children: "Usar como capa"
172
+ }
173
+ ),
174
+ /* @__PURE__ */ jsx(
175
+ AskAIButton,
176
+ {
177
+ size: "sm",
178
+ variant: "outline",
179
+ className: "h-6 px-2 text-xs",
180
+ label: "IA",
181
+ media: { url: image.url, name: image.altText ?? product.name },
182
+ draft: "Melhora essa imagem",
183
+ hint: "o que voc\xEA quer mudar nessa foto",
184
+ intro: {
185
+ persona: "Est\xFAdio de imagem",
186
+ pitch: `Vamos melhorar a foto de ${product.name}.`,
187
+ description: "Posso limpar o fundo, corrigir luz e cor, e gerar uma vers\xE3o em resolu\xE7\xE3o maior para o cat\xE1logo.",
188
+ suggestions: [
189
+ { label: "Fundo branco", prompt: "Remove o fundo e deixa branco" },
190
+ { label: "Mais n\xEDtida", prompt: "Aumenta a resolu\xE7\xE3o e a nitidez" }
191
+ ]
192
+ }
193
+ }
194
+ ),
195
+ /* @__PURE__ */ jsx(
196
+ Button,
197
+ {
198
+ variant: "bare",
199
+ size: "bare",
200
+ type: "button",
201
+ disabled: busy,
202
+ onClick: () => void run(() => getProvider().deleteProductImage(image.id)),
203
+ className: "font-normal rounded px-1.5 py-0.5 text-xs text-red-600 hover:bg-red-50 disabled:opacity-50",
204
+ children: "Excluir"
205
+ }
206
+ )
207
+ ] })
208
+ ] })
209
+ ] }, image.id)) }),
210
+ footer
211
+ ]
212
+ }
213
+ )
214
+ );
215
+ }
216
+ function IconButton({ label, disabled, onClick, children }) {
217
+ return /* @__PURE__ */ jsx(
218
+ Button,
219
+ {
220
+ variant: "bare",
221
+ size: "bare",
222
+ type: "button",
223
+ "aria-label": label,
224
+ title: label,
225
+ disabled,
226
+ onClick,
227
+ className: "font-normal disabled:pointer-events-auto rounded border px-1.5 py-0.5 text-xs text-muted-foreground transition hover:bg-muted hover:text-foreground disabled:opacity-40",
228
+ children
229
+ }
230
+ );
231
+ }
232
+ function sortImages(images) {
233
+ return [...images].sort((a, b) => {
234
+ if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
235
+ return (a.sortOrder ?? 0) - (b.sortOrder ?? 0);
236
+ });
237
+ }
238
+ function ProductContentTab({ item, getProvider, libraryHref = "#/content" }) {
239
+ const catalogId = item?.id;
240
+ const [product, setProduct] = useState(void 0);
241
+ const [error, setError] = useState(null);
242
+ useEffect(() => {
243
+ if (!catalogId) {
244
+ setProduct(null);
245
+ return;
246
+ }
247
+ let alive = true;
248
+ setProduct(void 0);
249
+ const provider = getProvider();
250
+ const lookup = provider.getProductByCatalogId ? provider.getProductByCatalogId(catalogId) : provider.getProduct(catalogId);
251
+ lookup.then((found) => {
252
+ if (alive) setProduct(found);
253
+ }).catch((err) => {
254
+ if (alive) {
255
+ setProduct(null);
256
+ setError(errorMessage(err));
257
+ }
258
+ });
259
+ return () => {
260
+ alive = false;
261
+ };
262
+ }, [catalogId]);
263
+ if (product === void 0) {
264
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", "aria-busy": "true", children: [
265
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
266
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-40" }),
267
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full max-w-lg" })
268
+ ] }),
269
+ /* @__PURE__ */ jsx("ul", { className: "fz-cards gap-3", children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
270
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-square w-full rounded-none" }),
271
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 py-2", children: [
272
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-14" }),
273
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
274
+ ] })
275
+ ] }, i)) })
276
+ ] });
277
+ }
278
+ if (!product) {
279
+ return /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground", children: error ?? "Este produto ainda n\xE3o est\xE1 na loja, ent\xE3o n\xE3o tem galeria. Publique-o na loja para adicionar fotos." });
280
+ }
281
+ return /* @__PURE__ */ jsx(
282
+ ProductImagesTab,
283
+ {
284
+ item: product,
285
+ getProvider,
286
+ footer: /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
287
+ "Estas fotos tamb\xE9m aparecem em",
288
+ " ",
289
+ /* @__PURE__ */ jsx("a", { href: appHref(libraryHref), className: "font-medium text-primary underline underline-offset-2 hover:opacity-80", children: "Conte\xFAdo" }),
290
+ ", junto com as de todos os produtos."
291
+ ] })
292
+ }
293
+ );
294
+ }
295
+
296
+ export { ProductContentTab, ProductImagesTab };
297
+ //# sourceMappingURL=chunk-JYT6BTRE.js.map
298
+ //# sourceMappingURL=chunk-JYT6BTRE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/views/ProductImagesTab.tsx","../src/views/ProductContentTab.tsx"],"names":["update","useState","useEffect","jsxs","jsx","Skeleton"],"mappings":"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","file":"chunk-JYT6BTRE.js","sourcesContent":["import { AskAIButton, DetailTabHeader } from '@fayz-ai/admin'\nimport { Button, Skeleton } from '@fayz-ai/ui'\nimport { Plus, UploadCloud } from 'lucide-react'\nimport React, { useEffect, useRef, useState } from 'react'\nimport { errorMessage } from '../settings/error-message'\nimport type { Product, ProductImage, ShopProvider } from '@fayz-ai/shop'\n\n/**\n * The product's whole gallery, not just its cover.\n *\n * plg_shop_product_images has always been a collection — sort_order, is_primary\n * — but the admin only ever exposed the single `EntityImageConfig` slot on the\n * form, so a merchant could set one photo and no more, and the storefront had\n * nothing to build a carousel from. This is the missing half.\n *\n * Deliberately not drag-and-drop: ordering is done with arrows because a\n * keyboard and a screen reader can use them, and because reordering four\n * photos of a rib is not worth a drag library.\n */\nexport function ProductImagesTab({ item, getProvider, footer }: {\n item: unknown\n getProvider: () => ShopProvider\n /** Under the gallery: where else this content lives. */\n footer?: React.ReactNode\n}) {\n const product = item as Product\n // `null` until the first read: an empty gallery and a gallery still loading are not the same screen.\n const [images, setImages] = useState<ProductImage[] | null>(null)\n const [dragging, setDragging] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const fileRef = useRef<HTMLInputElement>(null)\n\n async function reload() {\n const fresh = await getProvider().getProduct(product.id)\n setImages(sortImages(fresh?.images ?? []))\n }\n\n useEffect(() => {\n setImages(null)\n void reload().catch((err: unknown) => { setImages([]); setError(errorMessage(err)) })\n }, [product.id])\n\n async function run(action: () => Promise<unknown>) {\n setBusy(true)\n setError(null)\n try {\n await action()\n await reload()\n } catch (err: unknown) {\n setError(errorMessage(err))\n } finally {\n setBusy(false)\n }\n }\n\n async function upload(picked: FileList | File[] | null) {\n const files = Array.from(picked ?? []).filter((file) => file.type.startsWith('image/'))\n if (!files.length) return\n // Sequential, not Promise.all: each upload reads the current max sort_order\n // to place itself last, and running them together makes them all read the\n // same value and land on the same position.\n await run(async () => {\n for (const file of files) {\n await getProvider().uploadProductImage(product.id, file)\n }\n if (fileRef.current) fileRef.current.value = ''\n })\n }\n\n function move(index: number, delta: number) {\n const target = index + delta\n if (!images || target < 0 || target >= images.length) return\n const a = images[index]!\n const b = images[target]!\n // Swap the two positions rather than renumbering the list: fewer writes, and\n // a failure halfway leaves an order that is still coherent.\n void run(async () => {\n const update = getProvider().updateProductImage\n if (!update) throw new Error('Este provider não suporta reordenar imagens.')\n await update(a.id, { sortOrder: b.sortOrder ?? target })\n await update(b.id, { sortOrder: a.sortOrder ?? index })\n })\n }\n\n const update = getProvider().updateProductImage\n\n return (\n // The whole tab takes a drop: aiming at a small box with six files in hand\n // is the part of uploading that goes wrong.\n <div\n className=\"space-y-4\"\n onDragOver={(event) => { if (event.dataTransfer.types.includes('Files')) { event.preventDefault(); setDragging(true) } }}\n onDragLeave={(event) => { if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setDragging(false) }}\n onDrop={(event) => {\n if (!event.dataTransfer.files.length) return\n event.preventDefault()\n setDragging(false)\n void upload(event.dataTransfer.files)\n }}\n >\n <DetailTabHeader\n title=\"Fotos do produto\"\n description=\"A capa é a foto do produto em todo lugar: no cadastro, no estoque, no catálogo, no carrinho e no pedido. As demais viram o carrossel na página do produto.\"\n actions={\n // The same Button as every other tab. It used to be a <label> hand-\n // painted to look like one, which drifted the moment the real button\n // changed — different height, no icon, no disabled state.\n <Button disabled={busy} onClick={() => fileRef.current?.click()}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n {busy ? 'Enviando…' : 'Adicionar fotos'}\n </Button>\n }\n />\n\n {/* The picker itself: opened by the button, never shown. */}\n <input\n ref={fileRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n disabled={busy}\n className=\"hidden\"\n onChange={(event) => void upload(event.target.files)}\n />\n\n {error && (\n <p className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800\">{error}</p>\n )}\n\n {images === null ? (\n <ul className=\"fz-cards gap-3\" aria-busy=\"true\">\n {[0, 1, 2, 3].map((i) => (\n <li key={i} className=\"overflow-hidden rounded-lg border\">\n <Skeleton className=\"aspect-square w-full rounded-none\" />\n <div className=\"flex items-center justify-between gap-2 px-2 py-2\">\n <Skeleton className=\"h-4 w-14\" />\n <Skeleton className=\"h-4 w-20\" />\n </div>\n </li>\n ))}\n </ul>\n ) : images.length === 0 || dragging ? (\n <Button variant=\"bare\" size=\"bare\"\n type=\"button\"\n disabled={busy}\n onClick={() => fileRef.current?.click()}\n className={`font-normal whitespace-normal flex w-full flex-col items-center gap-2 rounded-lg border border-dashed px-4 py-10 text-center text-sm transition-colors ${\n dragging ? 'border-primary bg-primary/5 text-foreground' : 'text-muted-foreground hover:bg-muted/40'\n }`}\n >\n <UploadCloud className=\"h-6 w-6\" aria-hidden />\n <span className=\"font-medium\">{dragging ? 'Solte para enviar' : 'Arraste as fotos para cá, ou clique para escolher'}</span>\n {!dragging && (\n <span className=\"max-w-md text-xs\">\n Produtos com mais de uma foto vendem melhor — mostre a embalagem, o produto pronto e uma referência de tamanho.\n </span>\n )}\n </Button>\n ) : (\n <ul className=\"fz-cards gap-3\">\n {images.map((image, index) => (\n <li key={image.id} className=\"overflow-hidden rounded-lg border\">\n <div className=\"relative aspect-square bg-muted\">\n <img src={image.url} alt={image.altText ?? ''} className=\"h-full w-full object-cover\" />\n {image.isPrimary && (\n <span className=\"absolute left-2 top-2 rounded-full bg-primary px-2 py-0.5 text-[11px] font-bold text-primary-foreground\">\n Capa\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-center justify-between gap-x-1 gap-y-1.5 px-2 py-1.5\">\n <div className=\"flex gap-1\">\n <IconButton label=\"Mover para trás\" disabled={busy || index === 0}\n onClick={() => move(index, -1)}>←</IconButton>\n <IconButton label=\"Mover para frente\" disabled={busy || index === images.length - 1}\n onClick={() => move(index, 1)}>→</IconButton>\n </div>\n <div className=\"flex items-center gap-1\">\n {!image.isPrimary && update && (\n <Button variant=\"bare\" size=\"bare\"\n type=\"button\"\n disabled={busy}\n onClick={() => void run(() => update(image.id, { isPrimary: true }))}\n className=\"font-normal whitespace-nowrap rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50\"\n >\n Usar como capa\n </Button>\n )}\n <AskAIButton\n size=\"sm\"\n variant=\"outline\"\n className=\"h-6 px-2 text-xs\"\n label=\"IA\"\n media={{ url: image.url, name: image.altText ?? product.name }}\n draft=\"Melhora essa imagem\"\n hint=\"o que você quer mudar nessa foto\"\n intro={{\n persona: 'Estúdio de imagem',\n pitch: `Vamos melhorar a foto de ${product.name}.`,\n description: 'Posso limpar o fundo, corrigir luz e cor, e gerar uma versão em resolução maior para o catálogo.',\n suggestions: [\n { label: 'Fundo branco', prompt: 'Remove o fundo e deixa branco' },\n { label: 'Mais nítida', prompt: 'Aumenta a resolução e a nitidez' },\n ],\n }}\n />\n <Button variant=\"bare\" size=\"bare\"\n type=\"button\"\n disabled={busy}\n onClick={() => void run(() => getProvider().deleteProductImage(image.id))}\n className=\"font-normal rounded px-1.5 py-0.5 text-xs text-red-600 hover:bg-red-50 disabled:opacity-50\"\n >\n Excluir\n </Button>\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n {footer}\n </div>\n )\n}\n\nfunction IconButton({ label, disabled, onClick, children }: {\n label: string\n disabled?: boolean\n onClick: () => void\n children: React.ReactNode\n}) {\n return (\n <Button variant=\"bare\" size=\"bare\"\n type=\"button\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className=\"font-normal disabled:pointer-events-auto rounded border px-1.5 py-0.5 text-xs text-muted-foreground transition hover:bg-muted hover:text-foreground disabled:opacity-40\"\n >\n {children}\n </Button>\n )\n}\n\n/** Cover first, then the merchant's order — the same rule the storefront uses. */\nfunction sortImages(images: ProductImage[]): ProductImage[] {\n return [...images].sort((a, b) => {\n if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1\n return (a.sortOrder ?? 0) - (b.sortOrder ?? 0)\n })\n}\n","import React, { useEffect, useState } from 'react'\nimport { Skeleton } from '@fayz-ai/ui'\nimport type { Product, ShopProvider } from '@fayz-ai/shop'\nimport { ProductImagesTab } from './ProductImagesTab'\nimport { errorMessage } from '../settings/error-message'\nimport { appHref } from '@fayz-ai/core'\n\n/**\n * The store's content for a CATALOG product: its photos, which one is the\n * cover, and the way to the content library — mounted on the product record of\n * whichever plugin owns the catalog (stock, here), not only on the shop's own.\n *\n * `item.id` is the catalog product; the gallery belongs to the storefront\n * product behind it, which is looked up once.\n */\nexport function ProductContentTab({ item, getProvider, libraryHref = '#/content' }: {\n item: unknown\n getProvider: () => ShopProvider\n /** Where the whole library opens. */\n libraryHref?: string\n}) {\n const catalogId = (item as { id?: string } | null)?.id\n const [product, setProduct] = useState<Product | null | undefined>(undefined)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n if (!catalogId) { setProduct(null); return }\n let alive = true\n setProduct(undefined)\n const provider = getProvider()\n const lookup = provider.getProductByCatalogId\n ? provider.getProductByCatalogId(catalogId)\n : provider.getProduct(catalogId)\n lookup\n .then((found) => { if (alive) setProduct(found) })\n .catch((err: unknown) => { if (alive) { setProduct(null); setError(errorMessage(err)) } })\n return () => { alive = false }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [catalogId])\n\n if (product === undefined) {\n return (\n <div className=\"space-y-4\" aria-busy=\"true\">\n <div className=\"space-y-2\">\n <Skeleton className=\"h-5 w-40\" />\n <Skeleton className=\"h-4 w-full max-w-lg\" />\n </div>\n <ul className=\"fz-cards gap-3\">\n {[0, 1, 2, 3].map((i) => (\n <li key={i} className=\"overflow-hidden rounded-lg border\">\n <Skeleton className=\"aspect-square w-full rounded-none\" />\n <div className=\"flex items-center justify-between gap-2 px-2 py-2\">\n <Skeleton className=\"h-4 w-14\" />\n <Skeleton className=\"h-4 w-20\" />\n </div>\n </li>\n ))}\n </ul>\n </div>\n )\n }\n\n if (!product) {\n return (\n <p className=\"rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground\">\n {error ?? 'Este produto ainda não está na loja, então não tem galeria. Publique-o na loja para adicionar fotos.'}\n </p>\n )\n }\n\n return (\n <ProductImagesTab\n item={product}\n getProvider={getProvider}\n footer={(\n <p className=\"text-xs text-muted-foreground\">\n Estas fotos também aparecem em{' '}\n <a href={appHref(libraryHref)} className=\"font-medium text-primary underline underline-offset-2 hover:opacity-80\">Conteúdo</a>\n , junto com as de todos os produtos.\n </p>\n )}\n />\n )\n}\n"]}
@@ -0,0 +1,127 @@
1
+ import { errorMessage } from './chunk-2MXOKJQO.js';
2
+ import { useState, useEffect, useMemo } from 'react';
3
+ import { Skeleton, Badge, CurrencyInput, Button } from '@fayz-ai/ui';
4
+ import { appHref } from '@fayz-ai/core';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+
7
+ var SYMBOLS = { BRL: "R$", EUR: "\u20AC", USD: "US$", GBP: "\xA3", ARS: "AR$", MXN: "MX$" };
8
+ function ProductMarketPricesTab({ item, getProvider, settingsHref = "#/settings/shop-markets" }) {
9
+ const catalogId = item?.id;
10
+ const basePrice = Number(item?.salePrice ?? item?.price ?? 0);
11
+ const [markets, setMarkets] = useState(void 0);
12
+ const [saved, setSaved] = useState({});
13
+ const [draft, setDraft] = useState({});
14
+ const [error, setError] = useState(null);
15
+ const [saving, setSaving] = useState(false);
16
+ const [done, setDone] = useState(false);
17
+ useEffect(() => {
18
+ if (!catalogId) return;
19
+ let alive = true;
20
+ const provider = getProvider();
21
+ if (!provider.listAdminMarkets || !provider.getCatalogMarketPrices) {
22
+ setMarkets([]);
23
+ return;
24
+ }
25
+ Promise.all([provider.listAdminMarkets(), provider.getCatalogMarketPrices(catalogId)]).then(([list, prices]) => {
26
+ if (!alive) return;
27
+ const byMarket = Object.fromEntries(prices.map((p) => [p.marketId, p.price]));
28
+ setMarkets(list);
29
+ setSaved(byMarket);
30
+ setDraft(byMarket);
31
+ }).catch((err) => {
32
+ if (alive) {
33
+ setMarkets([]);
34
+ setError(errorMessage(err));
35
+ }
36
+ });
37
+ return () => {
38
+ alive = false;
39
+ };
40
+ }, [catalogId]);
41
+ const dirty = useMemo(
42
+ () => Object.keys(draft).some((id) => (draft[id] ?? null) !== (saved[id] ?? null)),
43
+ [draft, saved]
44
+ );
45
+ async function save() {
46
+ if (!catalogId) return;
47
+ const provider = getProvider();
48
+ if (!provider.setCatalogMarketPrice) return;
49
+ setSaving(true);
50
+ setError(null);
51
+ setDone(false);
52
+ try {
53
+ for (const id of Object.keys(draft)) {
54
+ const next = draft[id] ?? null;
55
+ if (next !== (saved[id] ?? null)) await provider.setCatalogMarketPrice(catalogId, id, next);
56
+ }
57
+ setSaved({ ...draft });
58
+ setDone(true);
59
+ } catch (err) {
60
+ setError(errorMessage(err));
61
+ } finally {
62
+ setSaving(false);
63
+ }
64
+ }
65
+ if (markets === void 0) {
66
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", "aria-busy": "true", children: [
67
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-48" }),
68
+ [0, 1].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-16 w-full" }, i))
69
+ ] });
70
+ }
71
+ if (markets.length === 0) {
72
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground", children: error ?? /* @__PURE__ */ jsxs(Fragment, { children: [
73
+ "A loja vende numa moeda s\xF3. Para vender em outros pa\xEDses com pre\xE7o pr\xF3prio,",
74
+ " ",
75
+ /* @__PURE__ */ jsx("a", { href: appHref(settingsHref), className: "font-medium text-primary underline underline-offset-2", children: "crie os mercados" }),
76
+ "."
77
+ ] }) });
78
+ }
79
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
80
+ /* @__PURE__ */ jsxs("div", { children: [
81
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Pre\xE7os por mercado" }),
82
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: "O pre\xE7o deste produto em cada mercado, na moeda dele. No mercado padr\xE3o, vazio usa o pre\xE7o de venda do produto; nos outros, vazio quer dizer que o produto n\xE3o \xE9 vendido l\xE1." })
83
+ ] }),
84
+ /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-lg border", children: markets.map((market) => {
85
+ const value = draft[market.id] ?? null;
86
+ const symbol = SYMBOLS[market.currency] ?? market.currency;
87
+ return /* @__PURE__ */ jsxs("li", { className: "flex flex-wrap items-center gap-4 px-4 py-3", children: [
88
+ /* @__PURE__ */ jsxs("div", { className: "min-w-[12rem] flex-1", children: [
89
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium", children: [
90
+ market.name,
91
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: market.currency }),
92
+ market.isDefault && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: "Padr\xE3o" }),
93
+ !market.isActive && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: "Inativo" })
94
+ ] }),
95
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: market.countries.length ? market.countries.join(", ") : "Todos os pa\xEDses sem outro mercado" })
96
+ ] }),
97
+ /* @__PURE__ */ jsx("div", { className: "w-48", children: /* @__PURE__ */ jsx(
98
+ CurrencyInput,
99
+ {
100
+ value: value ?? 0,
101
+ onChange: (next) => {
102
+ setDone(false);
103
+ setDraft((d) => ({ ...d, [market.id]: next > 0 ? next : null }));
104
+ },
105
+ symbol,
106
+ currencyCode: market.currency,
107
+ locale: market.locale ?? "pt-BR",
108
+ placeholder: market.isDefault && basePrice ? String(basePrice) : void 0
109
+ }
110
+ ) }),
111
+ /* @__PURE__ */ jsx("p", { className: "w-full text-xs sm:w-56", children: value != null ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Pre\xE7o pr\xF3prio neste mercado." }) : market.isDefault ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Usa o pre\xE7o de venda do produto." }) : /* @__PURE__ */ jsx("span", { className: "text-amber-600", children: "Sem pre\xE7o \u2014 n\xE3o aparece \xE0 venda neste mercado." }) })
112
+ ] }, market.id);
113
+ }) }),
114
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
115
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
116
+ /* @__PURE__ */ jsx(Button, { onClick: () => {
117
+ void save();
118
+ }, disabled: !dirty || saving, children: saving ? "Salvando\u2026" : "Salvar pre\xE7os" }),
119
+ done && !dirty && /* @__PURE__ */ jsx("span", { className: "text-sm text-emerald-600", children: "Pre\xE7os salvos." }),
120
+ /* @__PURE__ */ jsx("a", { href: appHref(settingsHref), className: "ml-auto text-xs text-muted-foreground underline underline-offset-2", children: "Gerenciar mercados" })
121
+ ] })
122
+ ] });
123
+ }
124
+
125
+ export { ProductMarketPricesTab };
126
+ //# sourceMappingURL=chunk-MFEOIJI7.js.map
127
+ //# sourceMappingURL=chunk-MFEOIJI7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/views/ProductMarketPricesTab.tsx"],"names":[],"mappings":"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","file":"chunk-MFEOIJI7.js","sourcesContent":["import React, { useEffect, useMemo, useState } from 'react'\nimport { Badge, Button, CurrencyInput, Skeleton } from '@fayz-ai/ui'\nimport type { AdminShopMarket, ShopProvider } from '@fayz-ai/shop'\nimport { errorMessage } from '../settings/error-message'\nimport { appHref } from '@fayz-ai/core'\n\nconst SYMBOLS: Record<string, string> = { BRL: 'R$', EUR: '€', USD: 'US$', GBP: '£', ARS: 'AR$', MXN: 'MX$' }\n\n/**\n * Preços por mercado (0116) — the product's price in each market, in that\n * market's money.\n *\n * Mounted on the CATALOGUE product (stock's record), because a price table\n * keys on public.products.id. Each market owns one price table; this tab writes\n * the product-level row of each. The default market falls back to the\n * product's own sale price when left empty; any other market without a price\n * does not sell the product — the storefront shows it sold out there and the\n * checkout refuses it, instead of charging the reais number in euros.\n */\nexport function ProductMarketPricesTab({ item, getProvider, settingsHref = '#/settings/shop-markets' }: {\n item: unknown\n getProvider: () => ShopProvider\n settingsHref?: string\n}) {\n const catalogId = (item as { id?: string } | null)?.id\n const basePrice = Number((item as { salePrice?: number; price?: number } | null)?.salePrice\n ?? (item as { price?: number } | null)?.price ?? 0)\n const [markets, setMarkets] = useState<AdminShopMarket[] | undefined>(undefined)\n const [saved, setSaved] = useState<Record<string, number | null>>({})\n const [draft, setDraft] = useState<Record<string, number | null>>({})\n const [error, setError] = useState<string | null>(null)\n const [saving, setSaving] = useState(false)\n const [done, setDone] = useState(false)\n\n useEffect(() => {\n if (!catalogId) return\n let alive = true\n const provider = getProvider()\n if (!provider.listAdminMarkets || !provider.getCatalogMarketPrices) {\n setMarkets([])\n return\n }\n Promise.all([provider.listAdminMarkets(), provider.getCatalogMarketPrices(catalogId)])\n .then(([list, prices]) => {\n if (!alive) return\n const byMarket = Object.fromEntries(prices.map((p) => [p.marketId, p.price]))\n setMarkets(list)\n setSaved(byMarket)\n setDraft(byMarket)\n })\n .catch((err: unknown) => { if (alive) { setMarkets([]); setError(errorMessage(err)) } })\n return () => { alive = false }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [catalogId])\n\n const dirty = useMemo(\n () => Object.keys(draft).some((id) => (draft[id] ?? null) !== (saved[id] ?? null)),\n [draft, saved],\n )\n\n async function save() {\n if (!catalogId) return\n const provider = getProvider()\n if (!provider.setCatalogMarketPrice) return\n setSaving(true)\n setError(null)\n setDone(false)\n try {\n for (const id of Object.keys(draft)) {\n const next = draft[id] ?? null\n if (next !== (saved[id] ?? null)) await provider.setCatalogMarketPrice(catalogId, id, next)\n }\n setSaved({ ...draft })\n setDone(true)\n } catch (err) {\n setError(errorMessage(err))\n } finally {\n setSaving(false)\n }\n }\n\n if (markets === undefined) {\n return (\n <div className=\"space-y-3\" aria-busy=\"true\">\n <Skeleton className=\"h-5 w-48\" />\n {[0, 1].map((i) => <Skeleton key={i} className=\"h-16 w-full\" />)}\n </div>\n )\n }\n\n if (markets.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground\">\n {error ?? (\n <>\n A loja vende numa moeda só. Para vender em outros países com preço próprio,{' '}\n <a href={appHref(settingsHref)} className=\"font-medium text-primary underline underline-offset-2\">crie os mercados</a>.\n </>\n )}\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n <div>\n <h3 className=\"text-sm font-semibold\">Preços por mercado</h3>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n O preço deste produto em cada mercado, na moeda dele. No mercado padrão, vazio usa o preço de venda do produto;\n nos outros, vazio quer dizer que o produto não é vendido lá.\n </p>\n </div>\n\n <ul className=\"divide-y rounded-lg border\">\n {markets.map((market) => {\n const value = draft[market.id] ?? null\n const symbol = SYMBOLS[market.currency] ?? market.currency\n return (\n <li key={market.id} className=\"flex flex-wrap items-center gap-4 px-4 py-3\">\n <div className=\"min-w-[12rem] flex-1\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n {market.name}\n <Badge variant=\"secondary\">{market.currency}</Badge>\n {market.isDefault && <Badge variant=\"outline\">Padrão</Badge>}\n {!market.isActive && <Badge variant=\"outline\">Inativo</Badge>}\n </div>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n {market.countries.length ? market.countries.join(', ') : 'Todos os países sem outro mercado'}\n </p>\n </div>\n <div className=\"w-48\">\n <CurrencyInput\n value={value ?? 0}\n onChange={(next) => { setDone(false); setDraft((d) => ({ ...d, [market.id]: next > 0 ? next : null })) }}\n symbol={symbol}\n currencyCode={market.currency}\n locale={market.locale ?? 'pt-BR'}\n placeholder={market.isDefault && basePrice ? String(basePrice) : undefined}\n />\n </div>\n <p className=\"w-full text-xs sm:w-56\">\n {value != null ? (\n <span className=\"text-muted-foreground\">Preço próprio neste mercado.</span>\n ) : market.isDefault ? (\n <span className=\"text-muted-foreground\">Usa o preço de venda do produto.</span>\n ) : (\n <span className=\"text-amber-600\">Sem preço — não aparece à venda neste mercado.</span>\n )}\n </p>\n </li>\n )\n })}\n </ul>\n\n {error && <p className=\"text-sm text-destructive\">{error}</p>}\n\n <div className=\"flex items-center gap-3\">\n <Button onClick={() => { void save() }} disabled={!dirty || saving}>\n {saving ? 'Salvando…' : 'Salvar preços'}\n </Button>\n {done && !dirty && <span className=\"text-sm text-emerald-600\">Preços salvos.</span>}\n <a href={appHref(settingsHref)} className=\"ml-auto text-xs text-muted-foreground underline underline-offset-2\">Gerenciar mercados</a>\n </div>\n </div>\n )\n}\n"]}
@@ -0,0 +1,61 @@
1
+ import { resolve } from './chunk-YUG6XQHJ.js';
2
+ import { errorMessage } from './chunk-2MXOKJQO.js';
3
+ import React from 'react';
4
+ import { getShopProvider } from '@fayz-ai/shop';
5
+
6
+ var DEBOUNCE_MS = 400;
7
+ function useStoreSettings(provider, defaults = {}) {
8
+ const shop = React.useMemo(
9
+ () => provider ? resolve(provider) : getShopProvider(),
10
+ [provider]
11
+ );
12
+ const supported = typeof shop.getStoreSettings === "function" && typeof shop.updateStoreSettings === "function";
13
+ const [stored, setStored] = React.useState(null);
14
+ const [loading, setLoading] = React.useState(supported);
15
+ const [saving, setSaving] = React.useState(false);
16
+ const [error, setError] = React.useState(null);
17
+ const [loadError, setLoadError] = React.useState(null);
18
+ React.useEffect(() => {
19
+ if (!supported) return;
20
+ let cancelled = false;
21
+ setLoading(true);
22
+ setLoadError(null);
23
+ void shop.getStoreSettings().then((settings) => {
24
+ if (!cancelled) setStored(settings);
25
+ }).catch((e) => {
26
+ if (!cancelled) setLoadError(errorMessage(e));
27
+ }).finally(() => {
28
+ if (!cancelled) setLoading(false);
29
+ });
30
+ return () => {
31
+ cancelled = true;
32
+ };
33
+ }, [shop, supported]);
34
+ const pendingRef = React.useRef({});
35
+ const timerRef = React.useRef(null);
36
+ React.useEffect(() => () => {
37
+ if (timerRef.current) clearTimeout(timerRef.current);
38
+ }, []);
39
+ const set = React.useCallback(
40
+ (patch) => {
41
+ setStored((current) => ({ ...current ?? {}, ...patch }));
42
+ if (!supported) return;
43
+ setError(null);
44
+ setSaving(true);
45
+ pendingRef.current = { ...pendingRef.current, ...patch };
46
+ if (timerRef.current) clearTimeout(timerRef.current);
47
+ timerRef.current = setTimeout(() => {
48
+ const payload = pendingRef.current;
49
+ pendingRef.current = {};
50
+ void shop.updateStoreSettings(payload).then((saved) => setStored(saved)).catch((e) => setError(errorMessage(e))).finally(() => setSaving(false));
51
+ }, DEBOUNCE_MS);
52
+ },
53
+ [shop, supported]
54
+ );
55
+ const value = React.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
56
+ return { value, stored, set, loading, saving, error, loadError, supported };
57
+ }
58
+
59
+ export { useStoreSettings };
60
+ //# sourceMappingURL=chunk-R5CUDFOI.js.map
61
+ //# sourceMappingURL=chunk-R5CUDFOI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/settings/useStoreSettings.ts"],"names":[],"mappings":"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","file":"chunk-R5CUDFOI.js","sourcesContent":["import React from 'react'\nimport { getShopProvider } from '@fayz-ai/shop'\nimport type { StoreSettings, UpdateStoreSettingsInput } from '@fayz-ai/shop'\nimport { resolve, type ShopProviderResolver } from '../data/shared'\nimport { errorMessage } from './error-message'\n\n// ---------------------------------------------------------------------------\n// useStoreSettings — the store's commercial policy, loaded and saved\n// ---------------------------------------------------------------------------\n// One hook behind all the Commerce settings screens, and the reason they can be\n// five screens instead of one: each renders a slice of the SAME tenant row, and\n// each save sends only the keys it touched (see storeSettingsPayload). Two\n// screens open in two tabs cannot overwrite each other's sections.\n//\n// AUTO-SAVE, no Save button. Every one of these is a single decision with an\n// immediate meaning (\"aceito boleto\"), and a form that batches ten of them\n// behind a button asks the merchant to remember which ones they changed. The\n// write is debounced so a burst of chip clicks collapses into one round trip.\n//\n// `undefined` is load-bearing throughout: it means the merchant has not decided,\n// which every reader treats as \"keep the app's own default\". A screen must\n// therefore never render `value ?? false` into the store — only into the\n// control. `defaults` exists for exactly that: it is what the UI shows while\n// nothing is chosen, and it is never written on its own.\n\nconst DEBOUNCE_MS = 400\n\nexport interface StoreSettingsHandle {\n /** Merged over `defaults`: what the controls render. */\n value: StoreSettings\n /** What is actually stored — `undefined` for anything undecided. */\n stored: StoreSettings | null\n set: (patch: UpdateStoreSettingsInput) => void\n loading: boolean\n /** True while a write is in flight or pending. Drives the \"saving…\" hint. */\n saving: boolean\n /** The provider refused the WRITE. Shown inline; the optimistic value stays\n * on screen so the merchant does not lose what they typed. */\n error: string | null\n /**\n * The provider refused the READ.\n *\n * Kept apart from `error` because the two need opposite copy and imply\n * opposite things. A failed save means \"your change did not stick\"; a failed\n * load means \"everything below is a default, not your settings\" — and showing\n * the save wording for a load failure, which is what this hook did at first,\n * tells the merchant a change was lost when they had not made one.\n */\n loadError: string | null\n /** False when the mounted provider has no store-settings support at all —\n * the screen says so instead of pretending the toggles work. */\n supported: boolean\n}\n\nexport function useStoreSettings(\n provider: ShopProviderResolver | undefined,\n defaults: StoreSettings = {},\n): StoreSettingsHandle {\n const shop = React.useMemo(\n () => (provider ? resolve(provider) : getShopProvider()),\n [provider],\n )\n const supported = typeof shop.getStoreSettings === 'function' && typeof shop.updateStoreSettings === 'function'\n\n const [stored, setStored] = React.useState<StoreSettings | null>(null)\n const [loading, setLoading] = React.useState(supported)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!supported) return\n let cancelled = false\n setLoading(true)\n setLoadError(null)\n void shop\n .getStoreSettings!()\n .then((settings) => { if (!cancelled) setStored(settings) })\n .catch((e) => { if (!cancelled) setLoadError(errorMessage(e)) })\n .finally(() => { if (!cancelled) setLoading(false) })\n return () => { cancelled = true }\n }, [shop, supported])\n\n // The pending patch accumulates across the debounce window, so clicking three\n // chips sends one write with three keys rather than three writes racing to be\n // last. Kept in a ref: a re-render must not restart the timer.\n const pendingRef = React.useRef<UpdateStoreSettingsInput>({})\n const timerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n React.useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current) }, [])\n\n const set = React.useCallback(\n (patch: UpdateStoreSettingsInput) => {\n // Optimistic: the control reflects the click without waiting for a server.\n setStored((current) => ({ ...(current ?? {}), ...patch }))\n if (!supported) return\n setError(null)\n setSaving(true)\n pendingRef.current = { ...pendingRef.current, ...patch }\n if (timerRef.current) clearTimeout(timerRef.current)\n timerRef.current = setTimeout(() => {\n const payload = pendingRef.current\n pendingRef.current = {}\n void shop\n .updateStoreSettings!(payload)\n // The server's answer wins over the optimistic guess — it is the row\n // that a CHECK constraint or a trigger may have had an opinion about.\n .then((saved) => setStored(saved))\n .catch((e) => setError(errorMessage(e)))\n .finally(() => setSaving(false))\n }, DEBOUNCE_MS)\n },\n [shop, supported],\n )\n\n const value = React.useMemo(() => ({ ...defaults, ...(stored ?? {}) }), [defaults, stored])\n\n return { value, stored, set, loading, saving, error, loadError, supported }\n}\n"]}