@fayz-ai/plugin-shop 0.9.0 → 0.10.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/README.md +4 -4
- package/dist/ShopPage-ZBJRNMKQ.js +2942 -0
- package/dist/ShopPage-ZBJRNMKQ.js.map +1 -0
- package/dist/analytics/cards.d.ts +3 -0
- package/dist/analytics/cards.d.ts.map +1 -0
- package/dist/chunk-EW4XJRA2.js +835 -0
- package/dist/chunk-EW4XJRA2.js.map +1 -0
- package/dist/data/categoryDataProvider.d.ts +5 -0
- package/dist/data/categoryDataProvider.d.ts.map +1 -0
- package/dist/data/collectionDataProvider.d.ts +5 -0
- package/dist/data/collectionDataProvider.d.ts.map +1 -0
- package/dist/data/customerGroupDataProvider.d.ts +5 -0
- package/dist/data/customerGroupDataProvider.d.ts.map +1 -0
- package/dist/data/customerSegmentDataProvider.d.ts +16 -0
- package/dist/data/customerSegmentDataProvider.d.ts.map +1 -0
- package/dist/data/giftCardDataProvider.d.ts +5 -0
- package/dist/data/giftCardDataProvider.d.ts.map +1 -0
- package/dist/data/registries.d.ts +53 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/data/registry-routes.d.ts +21 -0
- package/dist/data/registry-routes.d.ts.map +1 -0
- package/dist/data/shippingProfileDataProvider.d.ts +19 -0
- package/dist/data/shippingProfileDataProvider.d.ts.map +1 -0
- package/dist/data/stockLocationDataProvider.d.ts +13 -0
- package/dist/data/stockLocationDataProvider.d.ts.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5362 -418
- package/dist/index.js.map +1 -1
- package/dist/integrations/abacatepay/connectorDef.d.ts +41 -0
- package/dist/integrations/abacatepay/connectorDef.d.ts.map +1 -0
- package/dist/integrations/appmax/connectorDef.d.ts +28 -0
- package/dist/integrations/appmax/connectorDef.d.ts.map +1 -0
- package/dist/integrations/bling/connectorDef.d.ts +10 -0
- package/dist/integrations/bling/connectorDef.d.ts.map +1 -0
- package/dist/integrations/bling/data.d.ts +61 -0
- package/dist/integrations/bling/data.d.ts.map +1 -0
- package/dist/integrations/bling/inbound.d.ts +97 -0
- package/dist/integrations/bling/inbound.d.ts.map +1 -0
- package/dist/integrations/bling/index.d.ts +9 -0
- package/dist/integrations/bling/index.d.ts.map +1 -0
- package/dist/integrations/bling/limits.d.ts +33 -0
- package/dist/integrations/bling/limits.d.ts.map +1 -0
- package/dist/integrations/bling/mapping.d.ts +10 -0
- package/dist/integrations/bling/mapping.d.ts.map +1 -0
- package/dist/integrations/bling/sync.d.ts +70 -0
- package/dist/integrations/bling/sync.d.ts.map +1 -0
- package/dist/integrations/bling/types.d.ts +59 -0
- package/dist/integrations/bling/types.d.ts.map +1 -0
- package/dist/integrations/melhor-envio/connectorDef.d.ts +38 -0
- package/dist/integrations/melhor-envio/connectorDef.d.ts.map +1 -0
- package/dist/integrations/melhor-envio/labelProvider.d.ts +4 -0
- package/dist/integrations/melhor-envio/labelProvider.d.ts.map +1 -0
- package/dist/integrations/stripe-connect/connectorDef.d.ts +17 -0
- package/dist/integrations/stripe-connect/connectorDef.d.ts.map +1 -0
- package/dist/settings/CheckoutSettings.d.ts +17 -0
- package/dist/settings/CheckoutSettings.d.ts.map +1 -0
- package/dist/settings/ChoiceCards.d.ts +23 -0
- package/dist/settings/ChoiceCards.d.ts.map +1 -0
- package/dist/settings/FulfillmentSettings.d.ts +23 -0
- package/dist/settings/FulfillmentSettings.d.ts.map +1 -0
- package/dist/settings/HandlingSettings.d.ts +17 -0
- package/dist/settings/HandlingSettings.d.ts.map +1 -0
- package/dist/settings/PackagesSettings.d.ts +22 -0
- package/dist/settings/PackagesSettings.d.ts.map +1 -0
- package/dist/settings/PaymentProviderRow.d.ts +28 -0
- package/dist/settings/PaymentProviderRow.d.ts.map +1 -0
- package/dist/settings/PaymentsSettings.d.ts +7 -0
- package/dist/settings/PaymentsSettings.d.ts.map +1 -0
- package/dist/settings/SettingsState.d.ts +27 -0
- package/dist/settings/SettingsState.d.ts.map +1 -0
- package/dist/settings/ShippingSettings.d.ts +27 -0
- package/dist/settings/ShippingSettings.d.ts.map +1 -0
- package/dist/settings/StoreGeneralSettings.d.ts +19 -0
- package/dist/settings/StoreGeneralSettings.d.ts.map +1 -0
- package/dist/settings/error-message.d.ts +2 -0
- package/dist/settings/error-message.d.ts.map +1 -0
- package/dist/settings/index.d.ts +12 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/payment-provider.d.ts +13 -0
- package/dist/settings/payment-provider.d.ts.map +1 -0
- package/dist/settings/useStoreSettings.d.ts +30 -0
- package/dist/settings/useStoreSettings.d.ts.map +1 -0
- package/dist/views/AgencySelect.d.ts +17 -0
- package/dist/views/AgencySelect.d.ts.map +1 -0
- package/dist/views/ChannelsPage.d.ts +1 -1
- package/dist/views/ChannelsPage.d.ts.map +1 -1
- package/dist/views/ContentPage.d.ts +7 -0
- package/dist/views/ContentPage.d.ts.map +1 -0
- package/dist/views/EmailTemplatePage.d.ts +9 -0
- package/dist/views/EmailTemplatePage.d.ts.map +1 -0
- package/dist/views/EmailsPage.d.ts +7 -0
- package/dist/views/EmailsPage.d.ts.map +1 -0
- package/dist/views/FulfillmentProviderSelect.d.ts +14 -0
- package/dist/views/FulfillmentProviderSelect.d.ts.map +1 -0
- package/dist/views/NewOrderCharge.d.ts +17 -0
- package/dist/views/NewOrderCharge.d.ts.map +1 -0
- package/dist/views/NewOrderPage.d.ts +9 -0
- package/dist/views/NewOrderPage.d.ts.map +1 -0
- package/dist/views/OrderDetailView.d.ts +2 -0
- package/dist/views/OrderDetailView.d.ts.map +1 -1
- package/dist/views/OrderPaymentDetail.d.ts +5 -0
- package/dist/views/OrderPaymentDetail.d.ts.map +1 -0
- package/dist/views/OrderProviderLinks.d.ts +5 -0
- package/dist/views/OrderProviderLinks.d.ts.map +1 -0
- package/dist/views/PackageSelect.d.ts +20 -0
- package/dist/views/PackageSelect.d.ts.map +1 -0
- package/dist/views/ProductImagesTab.d.ts.map +1 -1
- package/dist/views/ProductVariantsTab.d.ts +29 -0
- package/dist/views/ProductVariantsTab.d.ts.map +1 -0
- package/dist/views/SenderLocationModal.d.ts +20 -0
- package/dist/views/SenderLocationModal.d.ts.map +1 -0
- package/dist/views/ShipOrderForm.d.ts +10 -0
- package/dist/views/ShipOrderForm.d.ts.map +1 -0
- package/dist/views/ShipWithCarrier.d.ts +18 -0
- package/dist/views/ShipWithCarrier.d.ts.map +1 -0
- package/dist/views/ShipmentActions.d.ts +11 -0
- package/dist/views/ShipmentActions.d.ts.map +1 -0
- package/dist/views/ShippingProfileSelect.d.ts +13 -0
- package/dist/views/ShippingProfileSelect.d.ts.map +1 -0
- package/dist/views/ShopPage.d.ts.map +1 -0
- package/dist/views/categoryEntity.d.ts +4 -0
- package/dist/views/categoryEntity.d.ts.map +1 -0
- package/dist/views/collectionEntity.d.ts +4 -0
- package/dist/views/collectionEntity.d.ts.map +1 -0
- package/dist/views/customerEntity.d.ts.map +1 -1
- package/dist/views/customerGroupEntity.d.ts +4 -0
- package/dist/views/customerGroupEntity.d.ts.map +1 -0
- package/dist/views/customerSegmentEntity.d.ts +13 -0
- package/dist/views/customerSegmentEntity.d.ts.map +1 -0
- package/dist/views/eta-label.d.ts +6 -0
- package/dist/views/eta-label.d.ts.map +1 -0
- package/dist/views/giftCardEntity.d.ts +4 -0
- package/dist/views/giftCardEntity.d.ts.map +1 -0
- package/dist/views/media-links.d.ts +12 -0
- package/dist/views/media-links.d.ts.map +1 -0
- package/dist/views/new-order-rules.d.ts +55 -0
- package/dist/views/new-order-rules.d.ts.map +1 -0
- package/dist/views/order-actions.d.ts +4 -0
- package/dist/views/order-actions.d.ts.map +1 -0
- package/dist/views/productEntity.d.ts.map +1 -1
- package/dist/views/shippingZoneEntity.d.ts.map +1 -1
- package/package.json +11 -10
- package/dist/ShopPage-JP27MZS2.js +0 -690
- package/dist/ShopPage-JP27MZS2.js.map +0 -1
- package/dist/ShopPage.d.ts.map +0 -1
- package/dist/components/ShopSettings.d.ts +0 -15
- package/dist/components/ShopSettings.d.ts.map +0 -1
- package/src/ShopPage.tsx +0 -425
- package/src/components/ShopSettings.tsx +0 -108
- package/src/data/customerDataProvider.ts +0 -31
- package/src/data/discountDataProvider.ts +0 -42
- package/src/data/productDataProvider.ts +0 -54
- package/src/data/shared.ts +0 -42
- package/src/data/shippingZoneDataProvider.ts +0 -49
- package/src/index.ts +0 -270
- package/src/views/ChannelsPage.tsx +0 -274
- package/src/views/OrderDetailView.tsx +0 -319
- package/src/views/ProductImagesTab.tsx +0 -183
- package/src/views/customerEntity.tsx +0 -110
- package/src/views/customerLookup.ts +0 -31
- package/src/views/discountEntity.tsx +0 -144
- package/src/views/productEntity.tsx +0 -229
- package/src/views/shippingZoneEntity.tsx +0 -123
- /package/dist/{ShopPage.d.ts → views/ShopPage.d.ts} +0 -0
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { SettingsGroup, ToggleRow, SelectRow } from '@fayz-ai/saas'
|
|
3
|
-
|
|
4
|
-
export interface ShopSettingsProps {
|
|
5
|
-
currency?: { code?: string; locale?: string; symbol?: string }
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const CURRENCIES = [
|
|
9
|
-
{ value: 'BRL', label: 'Real (R$)' },
|
|
10
|
-
{ value: 'USD', label: 'US Dollar ($)' },
|
|
11
|
-
{ value: 'EUR', label: 'Euro (€)' },
|
|
12
|
-
{ value: 'GBP', label: 'British Pound (£)' },
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
const LOCALES = [
|
|
16
|
-
{ value: 'pt-BR', label: 'Português (Brasil)' },
|
|
17
|
-
{ value: 'en-US', label: 'English (US)' },
|
|
18
|
-
{ value: 'es-ES', label: 'Español' },
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Shop plugin settings — store, catalog, checkout and notification config.
|
|
23
|
-
* Seeded from the plugin's currency option; interactive (local state). Wire to
|
|
24
|
-
* persisted plugin prefs when the shop settings store lands.
|
|
25
|
-
*/
|
|
26
|
-
export function ShopSettings({ currency }: ShopSettingsProps) {
|
|
27
|
-
const [code, setCode] = React.useState(currency?.code ?? 'BRL')
|
|
28
|
-
const [locale, setLocale] = React.useState(currency?.locale ?? 'pt-BR')
|
|
29
|
-
const [showOutOfStock, setShowOutOfStock] = React.useState(true)
|
|
30
|
-
const [trackInventory, setTrackInventory] = React.useState(true)
|
|
31
|
-
const [requireSku, setRequireSku] = React.useState(false)
|
|
32
|
-
const [guestCheckout, setGuestCheckout] = React.useState(true)
|
|
33
|
-
const [enableDiscounts, setEnableDiscounts] = React.useState(true)
|
|
34
|
-
const [orderEmails, setOrderEmails] = React.useState(true)
|
|
35
|
-
const [lowStockAlerts, setLowStockAlerts] = React.useState(false)
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div className="space-y-4">
|
|
39
|
-
<SettingsGroup title="Loja" description="Moeda e formatação da vitrine.">
|
|
40
|
-
<SelectRow
|
|
41
|
-
label="Moeda"
|
|
42
|
-
description="Moeda padrão para preços e checkout."
|
|
43
|
-
value={code}
|
|
44
|
-
options={CURRENCIES}
|
|
45
|
-
onChange={setCode}
|
|
46
|
-
/>
|
|
47
|
-
<SelectRow
|
|
48
|
-
label="Idioma / formato"
|
|
49
|
-
description="Formatação de números, preços e datas."
|
|
50
|
-
value={locale}
|
|
51
|
-
options={LOCALES}
|
|
52
|
-
onChange={setLocale}
|
|
53
|
-
/>
|
|
54
|
-
</SettingsGroup>
|
|
55
|
-
|
|
56
|
-
<SettingsGroup title="Catálogo" description="Como os produtos aparecem e são gerenciados.">
|
|
57
|
-
<ToggleRow
|
|
58
|
-
label="Mostrar produtos esgotados"
|
|
59
|
-
description="Exibir itens sem estoque na vitrine."
|
|
60
|
-
checked={showOutOfStock}
|
|
61
|
-
onChange={setShowOutOfStock}
|
|
62
|
-
/>
|
|
63
|
-
<ToggleRow
|
|
64
|
-
label="Controlar estoque"
|
|
65
|
-
description="Acompanhar quantidade e dar baixa no checkout."
|
|
66
|
-
checked={trackInventory}
|
|
67
|
-
onChange={setTrackInventory}
|
|
68
|
-
/>
|
|
69
|
-
<ToggleRow
|
|
70
|
-
label="Exigir SKU"
|
|
71
|
-
description="Tornar o SKU obrigatório ao criar produtos."
|
|
72
|
-
checked={requireSku}
|
|
73
|
-
onChange={setRequireSku}
|
|
74
|
-
/>
|
|
75
|
-
</SettingsGroup>
|
|
76
|
-
|
|
77
|
-
<SettingsGroup title="Checkout" description="Opções de compra e pagamento.">
|
|
78
|
-
<ToggleRow
|
|
79
|
-
label="Checkout sem cadastro"
|
|
80
|
-
description="Permitir compra como convidado."
|
|
81
|
-
checked={guestCheckout}
|
|
82
|
-
onChange={setGuestCheckout}
|
|
83
|
-
/>
|
|
84
|
-
<ToggleRow
|
|
85
|
-
label="Cupons de desconto"
|
|
86
|
-
description="Habilitar códigos de desconto no carrinho."
|
|
87
|
-
checked={enableDiscounts}
|
|
88
|
-
onChange={setEnableDiscounts}
|
|
89
|
-
/>
|
|
90
|
-
</SettingsGroup>
|
|
91
|
-
|
|
92
|
-
<SettingsGroup title="Notificações" description="Avisos por e-mail da loja.">
|
|
93
|
-
<ToggleRow
|
|
94
|
-
label="E-mail de novo pedido"
|
|
95
|
-
description="Notificar a equipe a cada novo pedido."
|
|
96
|
-
checked={orderEmails}
|
|
97
|
-
onChange={setOrderEmails}
|
|
98
|
-
/>
|
|
99
|
-
<ToggleRow
|
|
100
|
-
label="Alerta de estoque baixo"
|
|
101
|
-
description="Avisar quando produtos atingirem o mínimo."
|
|
102
|
-
checked={lowStockAlerts}
|
|
103
|
-
onChange={setLowStockAlerts}
|
|
104
|
-
/>
|
|
105
|
-
</SettingsGroup>
|
|
106
|
-
</div>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { CrudQuery, CrudResult, DataProvider } from '@fayz-ai/core'
|
|
2
|
-
import type { CreateCustomerInput, ShopCustomer, UpdateCustomerInput } from '@fayz-ai/shop'
|
|
3
|
-
import { applyQuery, resolve, type ShopProviderResolver } from './shared'
|
|
4
|
-
|
|
5
|
-
export function createShopCustomerDataProvider(
|
|
6
|
-
provider: ShopProviderResolver,
|
|
7
|
-
): DataProvider<ShopCustomer> {
|
|
8
|
-
return {
|
|
9
|
-
async list(query: CrudQuery): Promise<CrudResult<ShopCustomer>> {
|
|
10
|
-
const rows = await resolve(provider).listCustomers(
|
|
11
|
-
query.search ? { search: query.search } : undefined,
|
|
12
|
-
)
|
|
13
|
-
return applyQuery(rows, query)
|
|
14
|
-
},
|
|
15
|
-
|
|
16
|
-
async create(data): Promise<ShopCustomer> {
|
|
17
|
-
return resolve(provider).createCustomer(data as unknown as CreateCustomerInput)
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
async update(id: string, data: Partial<ShopCustomer>): Promise<ShopCustomer> {
|
|
21
|
-
// ordersCount / totalSpent are trigger-maintained (shop_refresh_customer_stats);
|
|
22
|
-
// never send them back or a stale form value would fight the database.
|
|
23
|
-
const { ordersCount: _o, totalSpent: _t, ...patch } = data
|
|
24
|
-
return resolve(provider).updateCustomer(id, patch as UpdateCustomerInput)
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
async remove(id: string): Promise<void> {
|
|
28
|
-
await resolve(provider).deleteCustomer(id)
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { CrudQuery, CrudResult, DataProvider } from '@fayz-ai/core'
|
|
2
|
-
import type { CreateDiscountInput, Discount, UpdateDiscountInput } from '@fayz-ai/shop'
|
|
3
|
-
import { applyQuery, resolve, type ShopProviderResolver } from './shared'
|
|
4
|
-
|
|
5
|
-
export function createShopDiscountDataProvider(
|
|
6
|
-
provider: ShopProviderResolver,
|
|
7
|
-
): DataProvider<Discount> {
|
|
8
|
-
return {
|
|
9
|
-
async list(query: CrudQuery): Promise<CrudResult<Discount>> {
|
|
10
|
-
const rows = await resolve(provider).listDiscounts()
|
|
11
|
-
const term = query.search?.trim().toLowerCase()
|
|
12
|
-
const filtered = term
|
|
13
|
-
? rows.filter((d) =>
|
|
14
|
-
d.title.toLowerCase().includes(term) || (d.code ?? '').toLowerCase().includes(term))
|
|
15
|
-
: rows
|
|
16
|
-
return applyQuery(filtered, query, 'status')
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
async create(data): Promise<Discount> {
|
|
20
|
-
const input = data as unknown as Partial<Discount>
|
|
21
|
-
return resolve(provider).createDiscount({
|
|
22
|
-
...(input as CreateDiscountInput),
|
|
23
|
-
// Codes are matched case-insensitively server-side; store them upper so
|
|
24
|
-
// the admin list reads consistently.
|
|
25
|
-
code: input.code ? String(input.code).trim().toUpperCase() : undefined,
|
|
26
|
-
} as CreateDiscountInput)
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
async update(id: string, data: Partial<Discount>): Promise<Discount> {
|
|
30
|
-
// timesUsed is incremented by shop_place_order inside the order transaction.
|
|
31
|
-
const { timesUsed: _used, ...patch } = data
|
|
32
|
-
if (patch.code !== undefined) {
|
|
33
|
-
patch.code = patch.code ? String(patch.code).trim().toUpperCase() : null
|
|
34
|
-
}
|
|
35
|
-
return resolve(provider).updateDiscount(id, patch as UpdateDiscountInput)
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
async remove(id: string): Promise<void> {
|
|
39
|
-
await resolve(provider).deleteDiscount(id)
|
|
40
|
-
},
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { CrudQuery, CrudResult, DataProvider } from '@fayz-ai/core'
|
|
2
|
-
import type { CreateProductInput, Product, UpdateProductInput } from '@fayz-ai/shop'
|
|
3
|
-
import { slugify } from '../views/productEntity'
|
|
4
|
-
import { applyQuery, resolve, type ShopProviderResolver } from './shared'
|
|
5
|
-
|
|
6
|
-
export type { ShopProviderResolver }
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Bridges the generic CRUD scaffolding (DataProvider) to the shop provider, so
|
|
10
|
-
// the product screens are the same createCrudPage machinery every other module
|
|
11
|
-
// uses instead of a bespoke form.
|
|
12
|
-
//
|
|
13
|
-
// Which shop provider is injected decides whether writes work at all:
|
|
14
|
-
// • createSupabaseShopProvider — authenticates as the merchant (their JWT),
|
|
15
|
-
// full CRUD, subject to the tenant-member RLS policies.
|
|
16
|
-
// • createFayzShopProvider — the storefront's anonymous REST client. Reads
|
|
17
|
-
// work; every write throws FayzShopError 501 by design. The form surfaces
|
|
18
|
-
// that message rather than pretending the save succeeded.
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
export function createShopProductDataProvider(provider: ShopProviderResolver): DataProvider<Product> {
|
|
22
|
-
return {
|
|
23
|
-
async list(query: CrudQuery): Promise<CrudResult<Product>> {
|
|
24
|
-
const rows = await resolve(provider).listProducts(
|
|
25
|
-
query.search ? { search: query.search } : undefined,
|
|
26
|
-
)
|
|
27
|
-
return applyQuery(rows, query, 'status')
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
async create(data): Promise<Product> {
|
|
31
|
-
const input = data as unknown as Partial<Product>
|
|
32
|
-
// The storefront URL is required by the table's UNIQUE(tenant_id, slug);
|
|
33
|
-
// deriving it from the name keeps the merchant from having to know that.
|
|
34
|
-
const slug = input.slug?.trim() || slugify(String(input.name ?? ''))
|
|
35
|
-
return resolve(provider).createProduct({
|
|
36
|
-
...(input as CreateProductInput),
|
|
37
|
-
slug,
|
|
38
|
-
} as CreateProductInput)
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
async update(id: string, data: Partial<Product>): Promise<Product> {
|
|
42
|
-
const patch = { ...data }
|
|
43
|
-
if (patch.slug !== undefined && !String(patch.slug).trim()) {
|
|
44
|
-
// Cleared on purpose → regenerate rather than write an empty slug.
|
|
45
|
-
patch.slug = slugify(String(patch.name ?? ''))
|
|
46
|
-
}
|
|
47
|
-
return resolve(provider).updateProduct(id, patch as UpdateProductInput)
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
async remove(id: string): Promise<void> {
|
|
51
|
-
await resolve(provider).deleteProduct(id)
|
|
52
|
-
},
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/data/shared.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { CrudQuery, CrudResult } from '@fayz-ai/core'
|
|
2
|
-
import type { ShopProvider } from '@fayz-ai/shop'
|
|
3
|
-
|
|
4
|
-
export type ShopProviderResolver = ShopProvider | (() => ShopProvider)
|
|
5
|
-
|
|
6
|
-
export const resolve = (p: ShopProviderResolver): ShopProvider => (typeof p === 'function' ? p() : p)
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Paging, sorting and facet filtering applied client-side: the shop provider's
|
|
10
|
-
* list methods take a search term but no offset, sort or filter parameters.
|
|
11
|
-
* Fine at storefront catalogue sizes; revisit if a tenant grows past a few
|
|
12
|
-
* thousand rows, where it should move into the query.
|
|
13
|
-
*/
|
|
14
|
-
export function applyQuery<T>(
|
|
15
|
-
rows: T[],
|
|
16
|
-
query: CrudQuery,
|
|
17
|
-
facetKey?: keyof T & string,
|
|
18
|
-
): CrudResult<T> {
|
|
19
|
-
const prop = (row: T, key: string): unknown => (row as Record<string, unknown>)[key]
|
|
20
|
-
let data = rows
|
|
21
|
-
|
|
22
|
-
if (facetKey) {
|
|
23
|
-
const wanted = query.filters?.[facetKey]
|
|
24
|
-
if (wanted && wanted !== 'all') data = data.filter((row) => prop(row, facetKey) === wanted)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (query.sortBy) {
|
|
28
|
-
const dir = query.sortDir === 'desc' ? -1 : 1
|
|
29
|
-
const key = query.sortBy
|
|
30
|
-
data = [...data].sort((a, b) => {
|
|
31
|
-
const av = prop(a, key)
|
|
32
|
-
const bv = prop(b, key)
|
|
33
|
-
if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir
|
|
34
|
-
return String(av ?? '').localeCompare(String(bv ?? ''), 'pt-BR') * dir
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const total = data.length
|
|
39
|
-
const page = query.page ?? 1
|
|
40
|
-
const size = query.pageSize ?? total
|
|
41
|
-
return { data: data.slice((page - 1) * size, page * size), total }
|
|
42
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { CrudQuery, CrudResult, DataProvider } from '@fayz-ai/core'
|
|
2
|
-
import type { CreateShippingZoneInput, ShippingZone, UpdateShippingZoneInput } from '@fayz-ai/shop'
|
|
3
|
-
import { applyQuery, resolve, type ShopProviderResolver } from './shared'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Delivery zones CRUD.
|
|
7
|
-
*
|
|
8
|
-
* The form takes CEPs however the merchant types them ('22.041-001', '22041001')
|
|
9
|
-
* and the provider normalises to the 8 bare digits shipping_zones compares on —
|
|
10
|
-
* asking a shop owner to strip punctuation is how a range silently stops
|
|
11
|
-
* matching and a whole city stops being served.
|
|
12
|
-
*/
|
|
13
|
-
export function createShopShippingZoneDataProvider(
|
|
14
|
-
provider: ShopProviderResolver,
|
|
15
|
-
): DataProvider<ShippingZone> {
|
|
16
|
-
const shop = () => {
|
|
17
|
-
const p = resolve(provider)
|
|
18
|
-
if (!p.listShippingZones) {
|
|
19
|
-
throw new Error('Este provider de loja não suporta zonas de entrega.')
|
|
20
|
-
}
|
|
21
|
-
return p
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
async list(query: CrudQuery): Promise<CrudResult<ShippingZone>> {
|
|
26
|
-
const rows = await shop().listShippingZones!()
|
|
27
|
-
const term = query.search?.trim().toLowerCase()
|
|
28
|
-
const filtered = term
|
|
29
|
-
? rows.filter((z) =>
|
|
30
|
-
z.name.toLowerCase().includes(term) || (z.carrier ?? '').toLowerCase().includes(term))
|
|
31
|
-
: rows
|
|
32
|
-
return applyQuery(filtered, query)
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
async create(data): Promise<ShippingZone> {
|
|
36
|
-
return shop().createShippingZone!(data as unknown as CreateShippingZoneInput)
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
async update(id: string, data: Partial<ShippingZone>): Promise<ShippingZone> {
|
|
40
|
-
// tenantId is set from the session on the server side; never from the form.
|
|
41
|
-
const { id: _id, tenantId: _tenant, ...patch } = data
|
|
42
|
-
return shop().updateShippingZone!(id, patch as UpdateShippingZoneInput)
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
async remove(id: string): Promise<void> {
|
|
46
|
-
await shop().deleteShippingZone!(id)
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'
|
|
3
|
-
import { setShopTenantResolver, getShopProvider } from '@fayz-ai/shop'
|
|
4
|
-
import { useOrganizationStore, createCrudPage, registerEntityPath } from '@fayz-ai/saas'
|
|
5
|
-
import { buildShopProductEntity } from './views/productEntity'
|
|
6
|
-
import { buildShopCustomerEntity } from './views/customerEntity'
|
|
7
|
-
import { buildShopDiscountEntity } from './views/discountEntity'
|
|
8
|
-
import { buildShopShippingZoneEntity } from './views/shippingZoneEntity'
|
|
9
|
-
import { createShopProductDataProvider } from './data/productDataProvider'
|
|
10
|
-
import { createShopCustomerDataProvider } from './data/customerDataProvider'
|
|
11
|
-
import { createShopDiscountDataProvider } from './data/discountDataProvider'
|
|
12
|
-
import { createShopShippingZoneDataProvider } from './data/shippingZoneDataProvider'
|
|
13
|
-
import type { ShopProviderResolver } from './ShopPage'
|
|
14
|
-
import { ShopSettings } from './components/ShopSettings'
|
|
15
|
-
import { ChannelsPage } from './views/ChannelsPage'
|
|
16
|
-
|
|
17
|
-
const ShopPage = React.lazy(() => import('./ShopPage').then((m) => ({ default: m.ShopPage })))
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Options
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
export interface ShopPluginOptions {
|
|
24
|
-
navPosition?: number
|
|
25
|
-
navSection?: 'main' | 'secondary' | 'settings'
|
|
26
|
-
navLabel?: string
|
|
27
|
-
/** Override the accordion child labels (Produtos / Pedidos / Clientes / Descontos). */
|
|
28
|
-
sectionLabels?: Partial<Record<'products' | 'orders' | 'customers' | 'discounts' | 'shipping', string>>
|
|
29
|
-
scope?: PluginScope
|
|
30
|
-
verticalId?: VerticalId
|
|
31
|
-
currency?: { code?: string; locale?: string; symbol?: string }
|
|
32
|
-
provider?: ShopProviderResolver
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Factory
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
export function createShopPlugin(options?: ShopPluginOptions): PluginManifest {
|
|
40
|
-
const curr = { code: 'BRL', locale: 'pt-BR', symbol: 'R$', ...options?.currency }
|
|
41
|
-
|
|
42
|
-
// Scope fallback shop queries to the active organization. Apps can inject a
|
|
43
|
-
// provider when they want SDK-owned data access instead of the global runtime.
|
|
44
|
-
setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id)
|
|
45
|
-
|
|
46
|
-
// Teach the shared person-path registry where a SHOP customer lives. The
|
|
47
|
-
// built-in map ships veterinary defaults (customer → /registry/tutors), so
|
|
48
|
-
// without this PersonLink would resolve a shop customer to the wrong module.
|
|
49
|
-
// Clientes now lives at the top level (/customers), out of the Loja accordion.
|
|
50
|
-
registerEntityPath('shop_customer', '/customers')
|
|
51
|
-
|
|
52
|
-
type ShopSection = 'products' | 'orders' | 'customers' | 'discounts' | 'shipping'
|
|
53
|
-
|
|
54
|
-
// One component per section so each gets its own route. The shell nests any
|
|
55
|
-
// entry whose route is a sub-path of another (same rule the courses modules
|
|
56
|
-
// use), so /shop/products … /shop/discounts render as an accordion under
|
|
57
|
-
// "Loja" instead of one link with everything hidden behind tabs.
|
|
58
|
-
const pageFor = (section?: ShopSection): React.ComponentType<unknown> => {
|
|
59
|
-
const Wrapped: React.ComponentType<unknown> = () =>
|
|
60
|
-
React.createElement(React.Suspense, { fallback: null },
|
|
61
|
-
React.createElement(
|
|
62
|
-
ShopPage as React.ComponentType<{ provider?: ShopProviderResolver; section?: ShopSection }>,
|
|
63
|
-
{ provider: options?.provider, section },
|
|
64
|
-
)
|
|
65
|
-
)
|
|
66
|
-
Wrapped.displayName = section ? `ShopPage:${section}` : 'ShopPageWrapper'
|
|
67
|
-
return Wrapped
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Products, customers and discounts all get the full generic CRUD (list +
|
|
71
|
-
// form + detail + delete) from their EntityDef, rather than three bespoke
|
|
72
|
-
// read-only tables. Orders stay on the custom page: an order is not a CRUD
|
|
73
|
-
// record — it needs fulfilment, refunds and a line-item view, which is its
|
|
74
|
-
// own screen.
|
|
75
|
-
const resolveProvider = () =>
|
|
76
|
-
typeof options?.provider === 'function'
|
|
77
|
-
? options.provider()
|
|
78
|
-
: options?.provider ?? getShopProvider()
|
|
79
|
-
|
|
80
|
-
const ProductsCrudPage = createCrudPage(buildShopProductEntity(curr.code, resolveProvider), {
|
|
81
|
-
dataProvider: createShopProductDataProvider(resolveProvider),
|
|
82
|
-
feature: 'shop',
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const CustomersCrudPage = createCrudPage(buildShopCustomerEntity(), {
|
|
86
|
-
dataProvider: createShopCustomerDataProvider(resolveProvider),
|
|
87
|
-
feature: 'shop',
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const DiscountsCrudPage = createCrudPage(buildShopDiscountEntity(), {
|
|
91
|
-
dataProvider: createShopDiscountDataProvider(resolveProvider),
|
|
92
|
-
feature: 'shop',
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const ShippingZonesCrudPage = createCrudPage(buildShopShippingZoneEntity(curr.code), {
|
|
96
|
-
dataProvider: createShopShippingZoneDataProvider(resolveProvider),
|
|
97
|
-
feature: 'shop',
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const CRUD_PAGES: Partial<Record<ShopSection, React.ComponentType<unknown>>> = {
|
|
101
|
-
products: ProductsCrudPage as React.ComponentType<unknown>,
|
|
102
|
-
customers: CustomersCrudPage as React.ComponentType<unknown>,
|
|
103
|
-
discounts: DiscountsCrudPage as React.ComponentType<unknown>,
|
|
104
|
-
shipping: ShippingZonesCrudPage as React.ComponentType<unknown>,
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// "Clientes" is intentionally NOT here: it moved out of the Loja accordion to
|
|
108
|
-
// a top-level nav item (/customers), registered separately below. The shell
|
|
109
|
-
// nests by route prefix, so a route under /shop is what put it inside "Loja".
|
|
110
|
-
const SECTIONS: Array<{ id: ShopSection; label: string; icon: string }> = [
|
|
111
|
-
{ id: 'products', label: options?.sectionLabels?.products ?? 'Produtos', icon: 'Package' },
|
|
112
|
-
{ id: 'orders', label: options?.sectionLabels?.orders ?? 'Pedidos', icon: 'ShoppingCart' },
|
|
113
|
-
{ id: 'discounts', label: options?.sectionLabels?.discounts ?? 'Descontos', icon: 'Tag' },
|
|
114
|
-
{ id: 'shipping', label: options?.sectionLabels?.shipping ?? 'Entrega', icon: 'Truck' },
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
// Standalone Customers page — same CRUD component, now at /customers so it
|
|
118
|
-
// reads as its own top-level destination instead of a Loja sub-tab.
|
|
119
|
-
const CustomersNavLabel = options?.sectionLabels?.customers ?? 'Clientes'
|
|
120
|
-
|
|
121
|
-
// Canais de venda — wraps the ChannelsPage with the app's shop provider.
|
|
122
|
-
const ChannelsPageWrapped: React.ComponentType<unknown> = () =>
|
|
123
|
-
React.createElement(ChannelsPage, { resolveProvider: options?.provider })
|
|
124
|
-
ChannelsPageWrapped.displayName = 'ChannelsPage'
|
|
125
|
-
|
|
126
|
-
const navSection = options?.navSection ?? 'main'
|
|
127
|
-
const navPosition = options?.navPosition ?? 6
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
id: 'shop',
|
|
131
|
-
name: options?.navLabel ?? 'Shop',
|
|
132
|
-
icon: 'ShoppingBag',
|
|
133
|
-
version: '1.0.0',
|
|
134
|
-
scope: options?.scope ?? 'vertical',
|
|
135
|
-
verticalId: options?.verticalId,
|
|
136
|
-
// Storefront/commerce-operator-console surface — targets the ecommerce
|
|
137
|
-
// Panel world, not a saas admin app.
|
|
138
|
-
scaffolds: ['ecommerce'],
|
|
139
|
-
defaultEnabled: true,
|
|
140
|
-
dependencies: [],
|
|
141
|
-
navigation: [
|
|
142
|
-
{
|
|
143
|
-
section: navSection,
|
|
144
|
-
position: navPosition,
|
|
145
|
-
label: options?.navLabel ?? 'Shop',
|
|
146
|
-
route: '/shop',
|
|
147
|
-
icon: 'ShoppingBag',
|
|
148
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
149
|
-
// "Loja" and "Produtos" are the same screen — don't list both.
|
|
150
|
-
indexChild: false,
|
|
151
|
-
},
|
|
152
|
-
...SECTIONS.map((s, i) => ({
|
|
153
|
-
section: navSection,
|
|
154
|
-
position: navPosition + i + 1,
|
|
155
|
-
label: s.label,
|
|
156
|
-
route: `/shop/${s.id}`,
|
|
157
|
-
icon: s.icon,
|
|
158
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
159
|
-
})),
|
|
160
|
-
// Clientes — top-level (route not under /shop, so it leaves the accordion).
|
|
161
|
-
// Fractional position keeps it right below "Loja", above other plugins.
|
|
162
|
-
{
|
|
163
|
-
section: navSection,
|
|
164
|
-
position: navPosition + 0.5,
|
|
165
|
-
label: CustomersNavLabel,
|
|
166
|
-
route: '/customers',
|
|
167
|
-
icon: 'Users',
|
|
168
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
169
|
-
},
|
|
170
|
-
// Canais de venda — inside the "Loja" group (route under /shop nests it in
|
|
171
|
-
// the accordion). The store's points of sale (Loja Online, WhatsApp, …).
|
|
172
|
-
{
|
|
173
|
-
section: navSection,
|
|
174
|
-
position: navPosition + SECTIONS.length + 1,
|
|
175
|
-
label: 'Canais de venda',
|
|
176
|
-
route: '/shop/channels',
|
|
177
|
-
icon: 'Radio',
|
|
178
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
routes: [
|
|
182
|
-
{
|
|
183
|
-
// Kept so existing links to /shop still resolve; renders the tabbed page.
|
|
184
|
-
path: '/shop',
|
|
185
|
-
component: pageFor(),
|
|
186
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
187
|
-
},
|
|
188
|
-
...SECTIONS.map((s) => ({
|
|
189
|
-
path: `/shop/${s.id}`,
|
|
190
|
-
component: CRUD_PAGES[s.id] ?? pageFor(s.id),
|
|
191
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
192
|
-
})),
|
|
193
|
-
// Clientes at the top level, same CRUD page as before.
|
|
194
|
-
{
|
|
195
|
-
path: '/customers',
|
|
196
|
-
component: CustomersCrudPage as React.ComponentType<unknown>,
|
|
197
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
198
|
-
},
|
|
199
|
-
// Canais de venda management page (under the Loja group).
|
|
200
|
-
{
|
|
201
|
-
path: '/shop/channels',
|
|
202
|
-
component: ChannelsPageWrapped,
|
|
203
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
widgets: [],
|
|
207
|
-
aiTools: [
|
|
208
|
-
{
|
|
209
|
-
id: 'shop.list-products',
|
|
210
|
-
name: 'listProducts',
|
|
211
|
-
description: 'Lists products in the shop catalog.',
|
|
212
|
-
icon: 'Package',
|
|
213
|
-
mode: 'read' as const,
|
|
214
|
-
category: 'Shop',
|
|
215
|
-
parameters: {
|
|
216
|
-
type: 'object' as const,
|
|
217
|
-
properties: {
|
|
218
|
-
search: { type: 'string' as const, description: 'Filter by name' },
|
|
219
|
-
status: { type: 'string' as const, enum: ['draft', 'active', 'archived'] },
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
suggestions: [
|
|
223
|
-
{ label: 'What products are available?' },
|
|
224
|
-
{ label: 'Which products are out of stock?' },
|
|
225
|
-
],
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
id: 'shop.list-orders',
|
|
229
|
-
name: 'listOrders',
|
|
230
|
-
description: 'Lists recent shop orders.',
|
|
231
|
-
icon: 'ShoppingCart',
|
|
232
|
-
mode: 'read' as const,
|
|
233
|
-
category: 'Shop',
|
|
234
|
-
parameters: {
|
|
235
|
-
type: 'object' as const,
|
|
236
|
-
properties: {
|
|
237
|
-
financialStatus: { type: 'string' as const, enum: ['pending', 'paid', 'refunded', 'voided'] },
|
|
238
|
-
fulfillmentStatus: { type: 'string' as const, enum: ['unfulfilled', 'partially_fulfilled', 'fulfilled'] },
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
suggestions: [
|
|
242
|
-
{ label: 'Show me pending orders' },
|
|
243
|
-
{ label: 'How many orders were paid today?' },
|
|
244
|
-
],
|
|
245
|
-
},
|
|
246
|
-
],
|
|
247
|
-
settings: [
|
|
248
|
-
{
|
|
249
|
-
id: 'shop',
|
|
250
|
-
label: 'Shop',
|
|
251
|
-
icon: 'ShoppingBag',
|
|
252
|
-
component: (() => {
|
|
253
|
-
const ShopSettingsTab: React.ComponentType<unknown> = () =>
|
|
254
|
-
React.createElement(ShopSettings, { currency: curr })
|
|
255
|
-
ShopSettingsTab.displayName = 'ShopSettingsTab'
|
|
256
|
-
return ShopSettingsTab
|
|
257
|
-
})(),
|
|
258
|
-
order: 30,
|
|
259
|
-
permission: { feature: 'shop', action: 'read' as const },
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Re-export shop types so consumer apps only need @fayz-ai/plugin-shop
|
|
266
|
-
export type {
|
|
267
|
-
Product, Order, ShopCustomer, Discount, Category,
|
|
268
|
-
CreateProductInput, UpdateProductInput, ListProductsOptions,
|
|
269
|
-
CreateOrderInput, UpdateOrderInput, ListOrdersOptions,
|
|
270
|
-
} from '@fayz-ai/shop'
|