@fayz-ai/plugin-shop 0.17.0 → 0.18.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.d.ts CHANGED
@@ -20,6 +20,14 @@ export interface ShopPluginOptions {
20
20
  * Pedidos, clientes, conteúdo e descontos continuam de pé.
21
21
  */
22
22
  products?: boolean;
23
+ /**
24
+ * The shop's own orders LIST at `/orders`. On by default. Turn it off when
25
+ * the app mounts an orders list that already covers every sale of the
26
+ * account: two plugins drawing `/orders` is one of them never showing.
27
+ * The shop keeps `/orders/new`, `/orders/:id/edit` and its detail page,
28
+ * which it offers through the `orders.detail` component slot.
29
+ */
30
+ orders?: boolean;
23
31
  };
24
32
  scope?: PluginScope;
25
33
  verticalId?: VerticalId;
@@ -48,6 +56,19 @@ export interface ShopPluginOptions {
48
56
  export declare function createShopPlugin(options?: ShopPluginOptions): PluginManifest;
49
57
  export * from './integrations/bling';
50
58
  export * from './integrations/neochef';
59
+ export { ProductContentTab } from './views/ProductContentTab';
60
+ import type { DetailTab } from '@fayz-ai/core';
61
+ import type { ShopProvider as ShopProviderForTab } from '@fayz-ai/shop';
62
+ /**
63
+ * The "Conteúdo" tab for a product record owned by another plugin: the app
64
+ * hands it to that plugin's own tab option. The shop offers; the app composes.
65
+ */
66
+ export declare function shopProductContentTab(options: {
67
+ provider: () => ShopProviderForTab;
68
+ /** Where the content library lives in this app. */
69
+ libraryHref?: string;
70
+ label?: string;
71
+ }): DetailTab;
51
72
  export { buildShopAnalyticsCards } from './analytics/cards';
52
73
  export type { Product, Order, ShopCustomer, Discount, Category, CreateProductInput, UpdateProductInput, ListProductsOptions, CreateOrderInput, UpdateOrderInput, ListOrdersOptions, } from '@fayz-ai/shop';
53
74
  export { melhorEnvioConnectorDef, MELHOR_ENVIO_CONNECTOR_ID, MELHOR_ENVIO_LATENCY_BUDGET_MS } from './integrations/melhor-envio/connectorDef';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAkC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAwB5D,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAC1B,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAC9D,aAAa,GAAG,YAAY,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAC3E,gBAAgB,GAAG,kBAAkB,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;IAC7D;;;OAGG;IACH,OAAO,CAAC,EAAE;QACR;;;;;;;WAOG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,CAAA;IACD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,QAAQ,CAAC,EAAE,oBAAoB,CAAA;IAC/B;oCACgC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;OAIG;IACH,cAAc,CAAC,EAAE,KAAK,CAAA;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAmlB5E;AAED,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AAKtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAG3D,YAAY,EACV,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAChD,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAC3D,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,GACtD,MAAM,eAAe,CAAA;AAItB,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,8BAA8B,EAAE,MAAM,0CAA0C,CAAA;AAC7I,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAA;AACtH,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAA;AACtI,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,gCAAgC,EAAE,MAAM,4CAA4C,CAAA;AACrJ,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAA;AAClH,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { shopCustomerLookup, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, SHIPPING_PROFILES_REGISTRY, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-6DRDPROL.js';
1
+ import { errorMessage, shopCustomerLookup, resolve, invalidateProductLists, listProductsDetailed, applyQuery, listProductsCached, resolveTerm, candidateMeta, SHIPPING_PROFILES_REGISTRY, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-6DRDPROL.js';
2
2
  import * as React13 from 'react';
3
3
  import React13__default, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
4
- import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getActiveTenantId, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, countByTenant, describeError, errorMessage as errorMessage$1, emitDataChanged, resolveDataProvider } from '@fayz-ai/core';
4
+ import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getActiveTenantId, getSupabaseClientOptional, registerMessageDefaults, registerComponent, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, countByTenant, describeError, errorMessage as errorMessage$1, emitDataChanged, resolveDataProvider } from '@fayz-ai/core';
5
5
  import { getShopTenantId, setShopTenantResolver, buildShopMessageDefaults, registerShippingLabelProvider, getShopProvider, buildShopDomainEvents, axesFromOptions, planVariantMatrix, validateVariantAxes, hasOnlyDefaultVariant, MAX_VARIANT_AXES, leavesNothingSellable, productImageToMediaFile, formatVariantCombination, applyVariantMatrix } from '@fayz-ai/shop';
6
- import { fayzConnectorOAuth, describeConnectorAccount, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, registerCrudNavTabs, adminNavigateTo, PageActions, DetailTabHeader, useAgentSurface, askAI, AskAIButton, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
7
- import { Package, Eye, FileEdit, PackageX, Users, UsersRound, Filter, UserX, Tag, TicketCheck, Layers, CreditCard, CircleDollarSign, Upload, ImageOff, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, AlertTriangle, X, ExternalLink, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, MapPin, Car, Truck, Info, Check, Sparkles, Ban, ListChecks, Search, Building2 } from 'lucide-react';
8
- import { PageTabs, PageHeaderActions, Card, Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Thumbnail, PageHeader, toast, ConfirmDialog, Hint, ModalBody, Skeleton } from '@fayz-ai/ui';
6
+ import { fayzConnectorOAuth, describeConnectorAccount, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, registerCrudNavTabs, adminNavigateTo, DetailTabHeader, AskAIButton, PageActions, useAgentSurface, askAI, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
7
+ import { Package, Eye, FileEdit, PackageX, Users, UsersRound, Filter, UserX, Tag, TicketCheck, Layers, CreditCard, CircleDollarSign, Plus, UploadCloud, Upload, ImageOff, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, AlertTriangle, X, ExternalLink, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, MapPin, Car, Truck, Info, Check, Sparkles, Ban, ListChecks, Search, Building2 } from 'lucide-react';
8
+ import { Skeleton, Button, PageTabs, PageHeaderActions, Card, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Thumbnail, PageHeader, toast, ConfirmDialog, Hint, ModalBody } from '@fayz-ai/ui';
9
9
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
10
  import { listStockReasonCodes, resolveStockStore, createSupabaseStockStore } from '@fayz-ai/shop/inventory';
11
11
  import { normalizeMovementType } from '@fayz-ai/plugin-inventory';
@@ -207,9 +207,10 @@ function createMelhorEnvioLabelProvider(resolve3) {
207
207
  track: async (orderId) => await resolve3().trackShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null
208
208
  };
209
209
  }
210
- function ProductImagesTab({ item, getProvider }) {
210
+ function ProductImagesTab({ item, getProvider, footer }) {
211
211
  const product = item;
212
- const [images, setImages] = useState([]);
212
+ const [images, setImages] = useState(null);
213
+ const [dragging, setDragging] = useState(false);
213
214
  const [busy, setBusy] = useState(false);
214
215
  const [error, setError] = useState(null);
215
216
  const fileRef = useRef(null);
@@ -218,7 +219,10 @@ function ProductImagesTab({ item, getProvider }) {
218
219
  setImages(sortImages(fresh?.images ?? []));
219
220
  }
220
221
  useEffect(() => {
221
- void reload().catch(() => {
222
+ setImages(null);
223
+ void reload().catch((err) => {
224
+ setImages([]);
225
+ setError(errorMessage(err));
222
226
  });
223
227
  }, [product.id]);
224
228
  async function run(action) {
@@ -233,10 +237,11 @@ function ProductImagesTab({ item, getProvider }) {
233
237
  setBusy(false);
234
238
  }
235
239
  }
236
- async function upload(files) {
237
- if (!files?.length) return;
240
+ async function upload(picked) {
241
+ const files = Array.from(picked ?? []).filter((file) => file.type.startsWith("image/"));
242
+ if (!files.length) return;
238
243
  await run(async () => {
239
- for (const file of Array.from(files)) {
244
+ for (const file of files) {
240
245
  await getProvider().uploadProductImage(product.id, file);
241
246
  }
242
247
  if (fileRef.current) fileRef.current.value = "";
@@ -244,7 +249,7 @@ function ProductImagesTab({ item, getProvider }) {
244
249
  }
245
250
  function move(index, delta) {
246
251
  const target = index + delta;
247
- if (target < 0 || target >= images.length) return;
252
+ if (!images || target < 0 || target >= images.length) return;
248
253
  const a = images[index];
249
254
  const b = images[target];
250
255
  void run(async () => {
@@ -255,87 +260,153 @@ function ProductImagesTab({ item, getProvider }) {
255
260
  });
256
261
  }
257
262
  const update = getProvider().updateProductImage;
258
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
259
- /* @__PURE__ */ jsx(
260
- DetailTabHeader,
261
- {
262
- title: "Fotos do produto",
263
- description: "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.",
264
- actions: (
265
- // The same Button as every other tab. It used to be a <label> hand-
266
- // painted to look like one, which drifted the moment the real button
267
- // changed — different height, no icon, no disabled state.
268
- /* @__PURE__ */ jsxs(Button, { disabled: busy, onClick: () => fileRef.current?.click(), children: [
269
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
270
- busy ? "Enviando\u2026" : "Adicionar fotos"
271
- ] })
272
- )
273
- }
274
- ),
275
- /* @__PURE__ */ jsx(
276
- "input",
263
+ return (
264
+ // The whole tab takes a drop: aiming at a small box with six files in hand
265
+ // is the part of uploading that goes wrong.
266
+ /* @__PURE__ */ jsxs(
267
+ "div",
277
268
  {
278
- ref: fileRef,
279
- type: "file",
280
- accept: "image/*",
281
- multiple: true,
282
- disabled: busy,
283
- className: "hidden",
284
- onChange: (event) => void upload(event.target.files)
285
- }
286
- ),
287
- 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 }),
288
- images.length === 0 ? /* @__PURE__ */ 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__ */ jsx("ul", { className: "fz-cards gap-3", children: images.map((image, index) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
289
- /* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted", children: [
290
- /* @__PURE__ */ jsx("img", { src: image.url, alt: image.altText ?? "", className: "h-full w-full object-cover" }),
291
- 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" })
292
- ] }),
293
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-1 px-2 py-1.5", children: [
294
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
269
+ className: "space-y-4",
270
+ onDragOver: (event) => {
271
+ if (event.dataTransfer.types.includes("Files")) {
272
+ event.preventDefault();
273
+ setDragging(true);
274
+ }
275
+ },
276
+ onDragLeave: (event) => {
277
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
278
+ },
279
+ onDrop: (event) => {
280
+ if (!event.dataTransfer.files.length) return;
281
+ event.preventDefault();
282
+ setDragging(false);
283
+ void upload(event.dataTransfer.files);
284
+ },
285
+ children: [
295
286
  /* @__PURE__ */ jsx(
296
- IconButton,
287
+ DetailTabHeader,
297
288
  {
298
- label: "Mover para tr\xE1s",
299
- disabled: busy || index === 0,
300
- onClick: () => move(index, -1),
301
- children: "\u2190"
289
+ title: "Fotos do produto",
290
+ 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.",
291
+ actions: (
292
+ // The same Button as every other tab. It used to be a <label> hand-
293
+ // painted to look like one, which drifted the moment the real button
294
+ // changed — different height, no icon, no disabled state.
295
+ /* @__PURE__ */ jsxs(Button, { disabled: busy, onClick: () => fileRef.current?.click(), children: [
296
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
297
+ busy ? "Enviando\u2026" : "Adicionar fotos"
298
+ ] })
299
+ )
302
300
  }
303
301
  ),
304
302
  /* @__PURE__ */ jsx(
305
- IconButton,
306
- {
307
- label: "Mover para frente",
308
- disabled: busy || index === images.length - 1,
309
- onClick: () => move(index, 1),
310
- children: "\u2192"
311
- }
312
- )
313
- ] }),
314
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
315
- !image.isPrimary && update && /* @__PURE__ */ jsx(
316
- "button",
303
+ "input",
317
304
  {
318
- type: "button",
305
+ ref: fileRef,
306
+ type: "file",
307
+ accept: "image/*",
308
+ multiple: true,
319
309
  disabled: busy,
320
- onClick: () => void run(() => update(image.id, { isPrimary: true })),
321
- className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50",
322
- children: "Usar como capa"
310
+ className: "hidden",
311
+ onChange: (event) => void upload(event.target.files)
323
312
  }
324
313
  ),
325
- /* @__PURE__ */ jsx(
314
+ 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 }),
315
+ 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: [
316
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-square w-full rounded-none" }),
317
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 py-2", children: [
318
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-14" }),
319
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
320
+ ] })
321
+ ] }, i)) }) : images.length === 0 || dragging ? /* @__PURE__ */ jsxs(
326
322
  "button",
327
323
  {
328
324
  type: "button",
329
325
  disabled: busy,
330
- onClick: () => void run(() => getProvider().deleteProductImage(image.id)),
331
- className: "rounded px-1.5 py-0.5 text-xs text-red-600 hover:bg-red-50 disabled:opacity-50",
332
- children: "Excluir"
326
+ onClick: () => fileRef.current?.click(),
327
+ className: `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"}`,
328
+ children: [
329
+ /* @__PURE__ */ jsx(UploadCloud, { className: "h-6 w-6", "aria-hidden": true }),
330
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: dragging ? "Solte para enviar" : "Arraste as fotos para c\xE1, ou clique para escolher" }),
331
+ !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." })
332
+ ]
333
333
  }
334
- )
335
- ] })
336
- ] })
337
- ] }, image.id)) })
338
- ] });
334
+ ) : /* @__PURE__ */ jsx("ul", { className: "fz-cards gap-3", children: images.map((image, index) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
335
+ /* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted", children: [
336
+ /* @__PURE__ */ jsx("img", { src: image.url, alt: image.altText ?? "", className: "h-full w-full object-cover" }),
337
+ 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" })
338
+ ] }),
339
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-1 gap-y-1.5 px-2 py-1.5", children: [
340
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
341
+ /* @__PURE__ */ jsx(
342
+ IconButton,
343
+ {
344
+ label: "Mover para tr\xE1s",
345
+ disabled: busy || index === 0,
346
+ onClick: () => move(index, -1),
347
+ children: "\u2190"
348
+ }
349
+ ),
350
+ /* @__PURE__ */ jsx(
351
+ IconButton,
352
+ {
353
+ label: "Mover para frente",
354
+ disabled: busy || index === images.length - 1,
355
+ onClick: () => move(index, 1),
356
+ children: "\u2192"
357
+ }
358
+ )
359
+ ] }),
360
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
361
+ !image.isPrimary && update && /* @__PURE__ */ jsx(
362
+ "button",
363
+ {
364
+ type: "button",
365
+ disabled: busy,
366
+ onClick: () => void run(() => update(image.id, { isPrimary: true })),
367
+ className: "whitespace-nowrap rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50",
368
+ children: "Usar como capa"
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsx(
372
+ AskAIButton,
373
+ {
374
+ size: "sm",
375
+ variant: "outline",
376
+ className: "h-6 px-2 text-xs",
377
+ label: "IA",
378
+ media: { url: image.url, name: image.altText ?? product.name },
379
+ draft: "Melhora essa imagem",
380
+ hint: "o que voc\xEA quer mudar nessa foto",
381
+ intro: {
382
+ persona: "Est\xFAdio de imagem",
383
+ pitch: `Vamos melhorar a foto de ${product.name}.`,
384
+ description: "Posso limpar o fundo, corrigir luz e cor, e gerar uma vers\xE3o em resolu\xE7\xE3o maior para o cat\xE1logo.",
385
+ suggestions: [
386
+ { label: "Fundo branco", prompt: "Remove o fundo e deixa branco" },
387
+ { label: "Mais n\xEDtida", prompt: "Aumenta a resolu\xE7\xE3o e a nitidez" }
388
+ ]
389
+ }
390
+ }
391
+ ),
392
+ /* @__PURE__ */ jsx(
393
+ "button",
394
+ {
395
+ type: "button",
396
+ disabled: busy,
397
+ onClick: () => void run(() => getProvider().deleteProductImage(image.id)),
398
+ className: "rounded px-1.5 py-0.5 text-xs text-red-600 hover:bg-red-50 disabled:opacity-50",
399
+ children: "Excluir"
400
+ }
401
+ )
402
+ ] })
403
+ ] })
404
+ ] }, image.id)) }),
405
+ footer
406
+ ]
407
+ }
408
+ )
409
+ );
339
410
  }
340
411
  function IconButton({ label, disabled, onClick, children }) {
341
412
  return /* @__PURE__ */ jsx(
@@ -8593,9 +8664,76 @@ function buildShopAnalyticsCards() {
8593
8664
  }
8594
8665
  ];
8595
8666
  }
8667
+ function ProductContentTab({ item, getProvider, libraryHref = "#/content" }) {
8668
+ const catalogId = item?.id;
8669
+ const [product, setProduct] = useState(void 0);
8670
+ const [error, setError] = useState(null);
8671
+ useEffect(() => {
8672
+ if (!catalogId) {
8673
+ setProduct(null);
8674
+ return;
8675
+ }
8676
+ let alive = true;
8677
+ setProduct(void 0);
8678
+ const provider = getProvider();
8679
+ const lookup = provider.getProductByCatalogId ? provider.getProductByCatalogId(catalogId) : provider.getProduct(catalogId);
8680
+ lookup.then((found) => {
8681
+ if (alive) setProduct(found);
8682
+ }).catch((err) => {
8683
+ if (alive) {
8684
+ setProduct(null);
8685
+ setError(errorMessage(err));
8686
+ }
8687
+ });
8688
+ return () => {
8689
+ alive = false;
8690
+ };
8691
+ }, [catalogId]);
8692
+ if (product === void 0) {
8693
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", "aria-busy": "true", children: [
8694
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
8695
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-40" }),
8696
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full max-w-lg" })
8697
+ ] }),
8698
+ /* @__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: [
8699
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-square w-full rounded-none" }),
8700
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 py-2", children: [
8701
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-14" }),
8702
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
8703
+ ] })
8704
+ ] }, i)) })
8705
+ ] });
8706
+ }
8707
+ if (!product) {
8708
+ 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." });
8709
+ }
8710
+ return /* @__PURE__ */ jsx(
8711
+ ProductImagesTab,
8712
+ {
8713
+ item: product,
8714
+ getProvider,
8715
+ footer: /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
8716
+ "Estas fotos tamb\xE9m aparecem em",
8717
+ " ",
8718
+ /* @__PURE__ */ jsx("a", { href: libraryHref, className: "font-medium text-primary underline underline-offset-2 hover:opacity-80", children: "Conte\xFAdo" }),
8719
+ ", junto com as de todos os produtos."
8720
+ ] })
8721
+ }
8722
+ );
8723
+ }
8596
8724
 
8597
8725
  // src/index.ts
8598
- var ShopPage = React13__default.lazy(() => import('./ShopPage-4B2NPTRT.js').then((m) => ({ default: m.ShopPage })));
8726
+ var ShopPage = React13__default.lazy(() => import('./ShopPage-J34V43NW.js').then((m) => ({ default: m.ShopPage })));
8727
+ var LazyOrderDetail = React13__default.lazy(() => import('./ShopPage-J34V43NW.js').then((m) => ({ default: m.ShopOrderDetailSlot })));
8728
+ function ShopOrderDetail(provider) {
8729
+ const Detail = (props) => React13__default.createElement(
8730
+ React13__default.Suspense,
8731
+ { fallback: null },
8732
+ React13__default.createElement(LazyOrderDetail, { ...props, provider })
8733
+ );
8734
+ Detail.displayName = "ShopOrderDetail";
8735
+ return Detail;
8736
+ }
8599
8737
  function createShopPlugin(options) {
8600
8738
  const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
8601
8739
  setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
@@ -8920,7 +9058,13 @@ function createShopPlugin(options) {
8920
9058
  ];
8921
9059
  const withProducts = options?.modules?.products !== false;
8922
9060
  const isProductRoute = (route) => route === "/products" || route.startsWith("/products/");
8923
- const ACTIVE_NAV = withProducts ? NAV : NAV.filter((entry) => !isProductRoute(entry.route));
9061
+ const withOrders = options?.modules?.orders !== false;
9062
+ const ACTIVE_NAV = NAV.filter((entry) => (withProducts || !isProductRoute(entry.route)) && (withOrders || entry.route !== "/orders"));
9063
+ const ORDER_FORM_ROUTES = [
9064
+ { path: "/orders/new", component: pageFor("orders") },
9065
+ { path: "/orders/:id/edit", component: pageFor("orders") }
9066
+ ];
9067
+ registerComponent("orders.detail", ShopOrderDetail(options?.provider));
8924
9068
  const ACTIVE_LEGACY = withProducts ? LEGACY_ROUTES : LEGACY_ROUTES.map((r) => r.path === "/shop" || r.path === "/shop/products" ? { path: r.path, component: redirectTo("/orders") } : r);
8925
9069
  const READ = { feature: "shop", action: "read" };
8926
9070
  return {
@@ -8951,6 +9095,7 @@ function createShopPlugin(options) {
8951
9095
  })),
8952
9096
  routes: [
8953
9097
  ...ACTIVE_NAV.map((entry) => ({ path: entry.route, component: entry.component, permission: READ })),
9098
+ ...ORDER_FORM_ROUTES.map((r) => ({ ...r, permission: READ })),
8954
9099
  ...HIDDEN_ROUTES.map((r) => ({ ...r, permission: READ })),
8955
9100
  ...ACTIVE_LEGACY.map((r) => ({ ...r, permission: READ }))
8956
9101
  ],
@@ -9097,7 +9242,16 @@ function createShopPlugin(options) {
9097
9242
  integrations: ["shopify", "stripe"]
9098
9243
  };
9099
9244
  }
9245
+ function shopProductContentTab(options) {
9246
+ return {
9247
+ id: "content",
9248
+ label: options.label ?? "Conte\xFAdo",
9249
+ icon: "Image",
9250
+ component: ProductContentTab,
9251
+ props: { getProvider: options.provider, libraryHref: options.libraryHref ?? "#/content" }
9252
+ };
9253
+ }
9100
9254
 
9101
- export { ABACATEPAY_CONNECTOR_ID, ABACATEPAY_LATENCY_BUDGET_MS, APPMAX_CONNECTOR_ID, APPMAX_LATENCY_BUDGET_MS, BLING_CONNECTOR_ID, BLING_MARKETPLACE_SOURCE, BLING_MAX_WINDOW_MS, BLING_PAGE_SIZE, BLING_RATE_LIMIT, BLING_SAMPLE_SIZE, BLING_SIGNATURE_HEADER, BLING_TOKEN_LEAD_MS, BLING_WEBHOOK_DEADLINE_MS, BLING_WINDOW_OVERLAP_MS, MELHOR_ENVIO_CONNECTOR_ID, MELHOR_ENVIO_LATENCY_BUDGET_MS, MERCADOPAGO_CONNECTOR_ID, MERCADOPAGO_LATENCY_BUDGET_MS, NEOCHEF_CONNECTOR_ID, NEOCHEF_CURSOR_OVERLAP_MS, NEOCHEF_FUNCTION, NEOCHEF_MARK, NEOCHEF_PAGE_SIZE, NEOCHEF_RATE_LIMIT, NEOCHEF_SAMPLE_SIZE, NEOCHEF_TOKEN_LEAD_MS, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, advanceNeoChefCursor, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, listOrderDeliveries2 as listOrderDeliveries, melhorEnvioConnectorDef, mercadoPagoConnectorDef, neoChefConnectionStore, neoChefConnectorDef, orphanConnection, pendingOrders2 as pendingOrders, pullCatalogue2 as pullCatalogue, pullProducts, pushOrders2 as pushOrders, reconcile2 as reconcile, routeCallback, sampleProducts, stripeConnectConnectorDef, testNeoChef, toMirroredNeoChefProduct, toMirroredProduct, toNeoChefOrder, vindiConnectorDef, windowFor };
9255
+ export { ABACATEPAY_CONNECTOR_ID, ABACATEPAY_LATENCY_BUDGET_MS, APPMAX_CONNECTOR_ID, APPMAX_LATENCY_BUDGET_MS, BLING_CONNECTOR_ID, BLING_MARKETPLACE_SOURCE, BLING_MAX_WINDOW_MS, BLING_PAGE_SIZE, BLING_RATE_LIMIT, BLING_SAMPLE_SIZE, BLING_SIGNATURE_HEADER, BLING_TOKEN_LEAD_MS, BLING_WEBHOOK_DEADLINE_MS, BLING_WINDOW_OVERLAP_MS, MELHOR_ENVIO_CONNECTOR_ID, MELHOR_ENVIO_LATENCY_BUDGET_MS, MERCADOPAGO_CONNECTOR_ID, MERCADOPAGO_LATENCY_BUDGET_MS, NEOCHEF_CONNECTOR_ID, NEOCHEF_CURSOR_OVERLAP_MS, NEOCHEF_FUNCTION, NEOCHEF_MARK, NEOCHEF_PAGE_SIZE, NEOCHEF_RATE_LIMIT, NEOCHEF_SAMPLE_SIZE, NEOCHEF_TOKEN_LEAD_MS, ProductContentTab, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, advanceNeoChefCursor, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, listOrderDeliveries2 as listOrderDeliveries, melhorEnvioConnectorDef, mercadoPagoConnectorDef, neoChefConnectionStore, neoChefConnectorDef, orphanConnection, pendingOrders2 as pendingOrders, pullCatalogue2 as pullCatalogue, pullProducts, pushOrders2 as pushOrders, reconcile2 as reconcile, routeCallback, sampleProducts, shopProductContentTab, stripeConnectConnectorDef, testNeoChef, toMirroredNeoChefProduct, toMirroredProduct, toNeoChefOrder, vindiConnectorDef, windowFor };
9102
9256
  //# sourceMappingURL=index.js.map
9103
9257
  //# sourceMappingURL=index.js.map