@fayz-ai/plugin-shop 0.10.0 → 0.11.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.js CHANGED
@@ -1,8 +1,8 @@
1
- import { resolve, applyQuery, errorMessage, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-EW4XJRA2.js';
1
+ import { shopCustomerLookup, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-HAKXHHAG.js';
2
2
  import React8, { useState, useEffect, useRef } from 'react';
3
3
  import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, resolveDataProvider } from '@fayz-ai/core';
4
- import { getShopTenantId, setShopTenantResolver, buildShopMessageDefaults, registerShippingLabelProvider, buildShopDomainEvents, axesFromOptions, planVariantMatrix, validateVariantAxes, hasOnlyDefaultVariant, MAX_VARIANT_AXES, leavesNothingSellable, getShopProvider, productImageToMediaFile, formatVariantCombination, applyVariantMatrix } from '@fayz-ai/shop';
5
- import { fayzConnectorOAuth, useOrganizationStore, registerEntityPath, createCrudPage, adminNavigateTo, ClientOrdersTab, createClientOrdersProvider, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
4
+ import { getShopTenantId, setShopTenantResolver, buildShopMessageDefaults, registerShippingLabelProvider, getShopProvider, buildShopDomainEvents, axesFromOptions, planVariantMatrix, validateVariantAxes, hasOnlyDefaultVariant, MAX_VARIANT_AXES, leavesNothingSellable, productImageToMediaFile, formatVariantCombination, applyVariantMatrix } from '@fayz-ai/shop';
5
+ import { fayzConnectorOAuth, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo, ClientOrdersTab, createClientOrdersProvider, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
6
6
  import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Link2, MapPin, Music, Camera, MessageCircle, Globe, Plus, Loader2, ChevronLeft, ChevronRight, Sparkles, Download, RefreshCw, Trash2, Upload, ExternalLink, Check, X, AlertTriangle, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, Car, Truck, Info } from 'lucide-react';
7
7
  import { HeaderTabs, Card, Button, Thumbnail, PageHeader, toast, ConfirmDialog, Input, Hint, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Skeleton } from '@fayz-ai/ui';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -2044,28 +2044,38 @@ function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
2044
2044
  function createShopProductDataProvider(provider) {
2045
2045
  return {
2046
2046
  async list(query) {
2047
- const rows = await resolve(provider).listProducts(
2047
+ const answer = await listProductsDetailed(
2048
+ provider,
2048
2049
  query.search ? { search: query.search } : void 0
2049
2050
  );
2050
- return applyQuery(rows, query, "status");
2051
+ return {
2052
+ ...applyQuery(answer.rows, query, "status"),
2053
+ ...answer.cached && { cached: true },
2054
+ ...answer.stale && { stale: true }
2055
+ };
2051
2056
  },
2052
2057
  async create(data) {
2053
2058
  const input = data;
2054
2059
  const slug = input.slug?.trim() || slugify(String(input.name ?? ""));
2055
- return resolve(provider).createProduct({
2060
+ const created = await resolve(provider).createProduct({
2056
2061
  ...input,
2057
2062
  slug
2058
2063
  });
2064
+ invalidateProductLists();
2065
+ return created;
2059
2066
  },
2060
2067
  async update(id, data) {
2061
2068
  const patch = { ...data };
2062
2069
  if (patch.slug !== void 0 && !String(patch.slug).trim()) {
2063
2070
  patch.slug = slugify(String(patch.name ?? ""));
2064
2071
  }
2065
- return resolve(provider).updateProduct(id, patch);
2072
+ const updated = await resolve(provider).updateProduct(id, patch);
2073
+ invalidateProductLists();
2074
+ return updated;
2066
2075
  },
2067
2076
  async remove(id) {
2068
2077
  await resolve(provider).deleteProduct(id);
2078
+ invalidateProductLists();
2069
2079
  }
2070
2080
  };
2071
2081
  }
@@ -3894,6 +3904,173 @@ var stripeConnectConnectorDef = {
3894
3904
  },
3895
3905
  ExtraPanel: StripeConnectExtraPanel
3896
3906
  };
3907
+ var VINDI_CONNECTOR_ID = "vindi";
3908
+ var VINDI_LATENCY_BUDGET_MS = 6e3;
3909
+ var VINDI_FIELDS = [
3910
+ {
3911
+ key: "tokenAccount",
3912
+ label: "Token da conta",
3913
+ type: "password",
3914
+ secret: true,
3915
+ placeholder: "Vindi Pagamentos \u2192 Minha Conta \u2192 Token"
3916
+ }
3917
+ ];
3918
+ var VINDI_ENVIRONMENT_SETTING = {
3919
+ key: "environment",
3920
+ label: "Ambiente",
3921
+ description: "Sandbox simula o pagamento e n\xE3o movimenta dinheiro. Produ\xE7\xE3o cobra o cliente de verdade.",
3922
+ type: "select",
3923
+ options: [
3924
+ { value: "sandbox", label: "Sandbox (teste)" },
3925
+ { value: "production", label: "Produ\xE7\xE3o" }
3926
+ ],
3927
+ requiredToOperate: true
3928
+ };
3929
+ var VINDI_SETTINGS = [
3930
+ VINDI_ENVIRONMENT_SETTING,
3931
+ {
3932
+ key: "methods",
3933
+ label: "Formas de pagamento",
3934
+ description: "O que o cliente pode escolher no checkout.",
3935
+ type: "multi-select",
3936
+ options: [
3937
+ { value: "27", label: "Pix" },
3938
+ { value: "28", label: "Bolepix (boleto com QR)" },
3939
+ { value: "6", label: "Boleto" }
3940
+ ],
3941
+ requiredToOperate: true
3942
+ }
3943
+ ];
3944
+ var store5 = createConnectionStore(VINDI_CONNECTOR_ID, {
3945
+ fields: VINDI_FIELDS,
3946
+ settings: VINDI_SETTINGS
3947
+ });
3948
+ var METHOD_LABELS4 = { "27": "Pix", "28": "Bolepix", "6": "Boleto" };
3949
+ var ENVIRONMENT_LABELS = { sandbox: "Sandbox", production: "Produ\xE7\xE3o" };
3950
+ function connectionDetail(settings) {
3951
+ const environment = ENVIRONMENT_LABELS[String(settings?.environment ?? "")];
3952
+ const raw = settings?.methods;
3953
+ const list = Array.isArray(raw) ? raw : typeof raw === "string" ? raw.split(",") : [];
3954
+ const methods = list.map((v) => METHOD_LABELS4[String(v).trim()] ?? String(v).trim()).filter(Boolean);
3955
+ const parts = [environment, methods.length > 0 ? methods.join(", ") : null].filter(Boolean);
3956
+ return parts.length > 0 ? parts.join(" \xB7 ") : void 0;
3957
+ }
3958
+ function VindiExtraPanel() {
3959
+ const [connected, setConnected] = useState(false);
3960
+ const [loaded, setLoaded] = useState(false);
3961
+ useEffect(() => {
3962
+ let cancelled = false;
3963
+ void store5.get().then((c) => {
3964
+ if (!cancelled) setConnected(!!c?.active);
3965
+ }).catch(() => {
3966
+ }).finally(() => {
3967
+ if (!cancelled) setLoaded(true);
3968
+ });
3969
+ return () => {
3970
+ cancelled = true;
3971
+ };
3972
+ }, []);
3973
+ if (!loaded) return null;
3974
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
3975
+ /* @__PURE__ */ jsx("p", { children: "Pix e boleto acontecem dentro da sua loja: o cliente v\xEA o QR Code ou a linha digit\xE1vel sem sair da p\xE1gina. Cart\xE3o ainda n\xE3o \u2014 ele vai chegar pela p\xE1gina segura da Vindi, para que nenhum n\xFAmero de cart\xE3o passe pelo seu site." }),
3976
+ /* @__PURE__ */ jsx("p", { children: "Sandbox e produ\xE7\xE3o s\xE3o contas diferentes, com tokens diferentes. Se voc\xEA trocar o ambiente aqui, troque tamb\xE9m as credenciais \u2014 as de teste n\xE3o cobram em produ\xE7\xE3o e as de produ\xE7\xE3o n\xE3o funcionam no sandbox." }),
3977
+ /* @__PURE__ */ jsx("p", { children: 'O valor cobrado \xE9 sempre recalculado aqui no servidor a partir do pedido. Pre\xE7o alterado no navegador n\xE3o \xE9 aceito, e clicar duas vezes em "pagar" gera uma cobran\xE7a s\xF3.' })
3978
+ ] }) : /* @__PURE__ */ jsx("p", { children: "O token da conta voc\xEA pega no painel da Vindi Pagamentos, em Minha Conta. Confira se ele \xE9 o do mesmo ambiente que voc\xEA vai escolher aqui \u2014 o token de sandbox n\xE3o cobra em produ\xE7\xE3o." }) });
3979
+ }
3980
+ var vindiConnectorDef = {
3981
+ id: VINDI_CONNECTOR_ID,
3982
+ hostPluginId: "shop",
3983
+ name: "Vindi",
3984
+ description: "Pix e boleto pela Vindi. A loja recebe na conta dela, com o cliente pagando sem sair da p\xE1gina.",
3985
+ icon: "CreditCard",
3986
+ // Declared rather than borrowed from the catalogue, because the catalogue row
3987
+ // is a change in another repo that has to be seeded before it exists anywhere:
3988
+ // until then `emit-connector-map` warns that `vindi` "resolves to a slug the
3989
+ // platform catalogue does not carry", and the shelf would render a grey plug.
3990
+ // The absolute URL on the assets bucket answers the same from any origin,
3991
+ // which a path would not — a tenant app is served from its own domain.
3992
+ logoUrl: "https://assets.fayz.ai/connectors/vindi.png",
3993
+ category: "payments",
3994
+ // Pasted keys, no consent screen: Vindi has no merchant-connect OAuth, so a
3995
+ // `startOAuth` here would ship a button that could never connect anything. See
3996
+ // the file header for why the flow that looks like OAuth is not one.
3997
+ authKind: "api-key",
3998
+ // The charge is opened in the buyer's request path; the notification and the
3999
+ // refund are outbound sync work.
4000
+ planes: ["request", "sync"],
4001
+ direction: "outbound",
4002
+ docsUrl: "https://developers.vindi.com.br/",
4003
+ // Where the token comes from. The consumer pair does NOT come from here — it
4004
+ // is requested from Vindi by form — which the fields' placeholders say.
4005
+ providerInstall: { url: "https://app.vindi.com.br" },
4006
+ // Grounded in what the request plane does, and padded with nothing: the total
4007
+ // is recomputed server-side from the order before the charge is opened, which
4008
+ // is a read of the order and never a write of it; the charge and its
4009
+ // confirmation are the write.
4010
+ permissions: [
4011
+ {
4012
+ kind: "data",
4013
+ resource: "order",
4014
+ actions: ["read"],
4015
+ reason: "Ler o pedido para recalcular o valor no servidor antes de abrir a cobran\xE7a \u2014 o total nunca vem do navegador do cliente.",
4016
+ sensitive: true
4017
+ },
4018
+ {
4019
+ kind: "data",
4020
+ resource: "payment",
4021
+ actions: ["write"],
4022
+ reason: "Registrar aqui a cobran\xE7a aberta e a confirma\xE7\xE3o do pagamento. Dados do cart\xE3o n\xE3o passam por este app.",
4023
+ sensitive: true
4024
+ }
4025
+ ],
4026
+ latencyBudgetMs: VINDI_LATENCY_BUDGET_MS,
4027
+ // A gateway that does not answer must stop the sale: an order that reads as
4028
+ // confirmed without a payment is worse than a refused checkout. Same verdict
4029
+ // as the other three gateways, opposite to the carrier next door.
4030
+ onFailure: "block",
4031
+ fields: VINDI_FIELDS,
4032
+ settings: VINDI_SETTINGS,
4033
+ invokedAt: [
4034
+ {
4035
+ key: "shop.checkout.session_open",
4036
+ label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na Vindi (Pix ou boleto)",
4037
+ touches: ["order", "payment"]
4038
+ },
4039
+ {
4040
+ key: "shop.payment.capture",
4041
+ label: "A Vindi avisa que o pagamento caiu, a cobran\xE7a \xE9 reconsultada e o pedido \xE9 liberado",
4042
+ plane: "sync",
4043
+ trigger: "webhook",
4044
+ touches: ["payment"]
4045
+ }
4046
+ ],
4047
+ // Read from the row the list already batched, so a screen with N connectors
4048
+ // costs one plg_connections request rather than N.
4049
+ ...connectionStatus(
4050
+ VINDI_CONNECTOR_ID,
4051
+ (connection) => ({
4052
+ connected: !!connection?.active,
4053
+ detail: connectionDetail(connection?.settings),
4054
+ plane: "request",
4055
+ ...settingsStatus(VINDI_SETTINGS, connection)
4056
+ }),
4057
+ { store: () => store5 }
4058
+ ),
4059
+ ...connectionSettings(store5),
4060
+ // Reached only AFTER the credential sink accepted the three keys — that
4061
+ // ordering is what makes `active = true` mean "credentials were placed for
4062
+ // this store" rather than "somebody pressed a button". They never arrive here:
4063
+ // the hub split them out before calling this, so `values` is empty.
4064
+ //
4065
+ // No `testConnection` either: the probe is handed the non-secret half, which
4066
+ // for this connector is nothing. Testing belongs to the edge function that
4067
+ // holds the credential.
4068
+ saveConnection: async () => {
4069
+ await store5.save({ active: true });
4070
+ },
4071
+ disconnect: () => store5.disconnect(),
4072
+ ExtraPanel: VindiExtraPanel
4073
+ };
3897
4074
  var KIND_META = {
3898
4075
  online_store: { label: "Loja Online", icon: Globe, hint: "Seu storefront fayz \u2014 a vitrine p\xFAblica da loja.", placeholder: "https://minhaloja.com.br/" },
3899
4076
  whatsapp: { label: "WhatsApp", icon: MessageCircle, hint: "Link direto para o WhatsApp Business.", placeholder: "https://wa.me/5521999999999" },
@@ -4657,7 +4834,7 @@ function reconcile() {
4657
4834
 
4658
4835
  // src/integrations/bling/connectorDef.tsx
4659
4836
  var blingConnectionStore = () => createConnectionStore(BLING_CONNECTOR_ID, {
4660
- settingsKeys: ["companyId", "backfillFrom", "stockOwner", "source"]
4837
+ settingsKeys: ["companyId", "backfillFrom", "stockOwner", "source", "pushOrdersFrom"]
4661
4838
  });
4662
4839
  var blingConnectorDef = {
4663
4840
  id: BLING_CONNECTOR_ID,
@@ -5762,7 +5939,16 @@ function buildShopAnalyticsCards() {
5762
5939
  query: {
5763
5940
  source: { kind: "view", name: CUSTOMERS },
5764
5941
  dateColumn: null,
5765
- dimensions: [{ key: "customer_name", label: tl("Customer", "Cliente") }],
5942
+ dimensions: [
5943
+ {
5944
+ key: "customer_name",
5945
+ label: tl("Customer", "Cliente"),
5946
+ person: { idKey: "customer_id", kind: "shop_customer" }
5947
+ },
5948
+ // Grouped on but never shown. It is what the name links to — and also
5949
+ // what stops two customers called "Silva" from being reported as one.
5950
+ { key: "customer_id", label: tl("Customer id", "ID do cliente"), hidden: true }
5951
+ ],
5766
5952
  measures: [
5767
5953
  { key: "total_spent", label: tl("Total spent", "Total gasto"), agg: "sum", format: "currency" },
5768
5954
  { key: "orders_count", label: tl("Orders", "Pedidos"), agg: "sum", format: "number" },
@@ -5797,7 +5983,13 @@ function buildShopAnalyticsCards() {
5797
5983
  columns: [
5798
5984
  { key: "created_at", label: tl("Date", "Data"), type: "date", sortable: true },
5799
5985
  { key: "order_number", label: tl("Order", "Pedido"), type: "number", sortable: true },
5800
- { key: "customer_name", label: tl("Customer", "Cliente"), type: "text" },
5986
+ {
5987
+ key: "customer_name",
5988
+ label: tl("Customer", "Cliente"),
5989
+ type: "person",
5990
+ idKey: "customer_id",
5991
+ personKind: "shop_customer"
5992
+ },
5801
5993
  { key: "customer_email", label: tl("Email", "E-mail"), type: "email" },
5802
5994
  { key: "financial_status", label: tl("Payment", "Pagamento"), type: "text" },
5803
5995
  { key: "fulfillment_status", label: tl("Fulfillment", "Entrega"), type: "text" },
@@ -5884,7 +6076,13 @@ function buildShopAnalyticsCards() {
5884
6076
  },
5885
6077
  viz: { type: "table" },
5886
6078
  columns: [
5887
- { key: "customer_name", label: tl("Customer", "Cliente"), type: "text" },
6079
+ {
6080
+ key: "customer_name",
6081
+ label: tl("Customer", "Cliente"),
6082
+ type: "person",
6083
+ idKey: "customer_id",
6084
+ personKind: "shop_customer"
6085
+ },
5888
6086
  { key: "email", label: tl("Email", "E-mail"), type: "email" },
5889
6087
  { key: "created_at", label: tl("Since", "Cliente desde"), type: "date", sortable: true },
5890
6088
  { key: "orders_count", label: tl("Orders", "Pedidos"), type: "number", aggregate: "sum", sortable: true },
@@ -5897,13 +6095,14 @@ function buildShopAnalyticsCards() {
5897
6095
  }
5898
6096
 
5899
6097
  // src/index.ts
5900
- var ShopPage = React8.lazy(() => import('./ShopPage-ZBJRNMKQ.js').then((m) => ({ default: m.ShopPage })));
6098
+ var ShopPage = React8.lazy(() => import('./ShopPage-YB5ZWECJ.js').then((m) => ({ default: m.ShopPage })));
5901
6099
  function createShopPlugin(options) {
5902
6100
  const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
5903
6101
  setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
5904
6102
  registerMessageDefaults(buildShopMessageDefaults());
5905
6103
  registerShippingLabelProvider(createMelhorEnvioLabelProvider(() => typeof options?.provider === "function" ? options.provider() : options?.provider ?? getShopProvider()));
5906
6104
  registerEntityPath("shop_customer", "/orders/customers");
6105
+ registerPersonLookup("shop_customer", shopCustomerLookup(() => resolveProvider()));
5907
6106
  const pageFor = (section) => {
5908
6107
  const Wrapped = () => React8.createElement(
5909
6108
  React8.Suspense,
@@ -6339,6 +6538,7 @@ function createShopPlugin(options) {
6339
6538
  appmaxConnectorDef,
6340
6539
  abacatePayConnectorDef,
6341
6540
  stripeConnectConnectorDef,
6541
+ vindiConnectorDef,
6342
6542
  blingConnectorDef
6343
6543
  ],
6344
6544
  // What having a store makes plugable — the four above plus the ones still
@@ -6349,6 +6549,6 @@ function createShopPlugin(options) {
6349
6549
  };
6350
6550
  }
6351
6551
 
6352
- 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, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, melhorEnvioConnectorDef, orphanConnection, pullProducts, routeCallback, sampleProducts, stripeConnectConnectorDef, toMirroredProduct, windowFor };
6552
+ 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, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, melhorEnvioConnectorDef, orphanConnection, pullProducts, routeCallback, sampleProducts, stripeConnectConnectorDef, toMirroredProduct, vindiConnectorDef, windowFor };
6353
6553
  //# sourceMappingURL=index.js.map
6354
6554
  //# sourceMappingURL=index.js.map