@fayz-ai/plugin-shop 0.24.5 → 0.24.7
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/CheckoutSettings-GQRYZF6I.js +128 -0
- package/dist/CheckoutSettings-GQRYZF6I.js.map +1 -0
- package/dist/ContentPage-2PHOZSFZ.js +510 -0
- package/dist/ContentPage-2PHOZSFZ.js.map +1 -0
- package/dist/DeliveryStoreSettings-L2RXOVB6.js +172 -0
- package/dist/DeliveryStoreSettings-L2RXOVB6.js.map +1 -0
- package/dist/FulfillmentSettings-PW233UOK.js +61 -0
- package/dist/FulfillmentSettings-PW233UOK.js.map +1 -0
- package/dist/MarketsSettings-YUEVDLIR.js +19 -0
- package/dist/MarketsSettings-YUEVDLIR.js.map +1 -0
- package/dist/PaymentsSettings-5CI74TY2.js +318 -0
- package/dist/PaymentsSettings-5CI74TY2.js.map +1 -0
- package/dist/ProductContentTab-PSNDS7JZ.js +4 -0
- package/dist/ProductContentTab-PSNDS7JZ.js.map +1 -0
- package/dist/ProductListingTab-55Y5RXU7.js +4 -0
- package/dist/ProductListingTab-55Y5RXU7.js.map +1 -0
- package/dist/ProductMarketPricesTab-QD2ZXEWL.js +4 -0
- package/dist/ProductMarketPricesTab-QD2ZXEWL.js.map +1 -0
- package/dist/ShippingSettings-UBTNRBJ4.js +9 -0
- package/dist/ShippingSettings-UBTNRBJ4.js.map +1 -0
- package/dist/{ShopPage-NUIIWBCB.js → ShopPage-OUV3FITM.js} +21 -7
- package/dist/ShopPage-OUV3FITM.js.map +1 -0
- package/dist/StoreGeneralSettings-DEZ2CMVG.js +97 -0
- package/dist/StoreGeneralSettings-DEZ2CMVG.js.map +1 -0
- package/dist/chunk-2MXOKJQO.js +23 -0
- package/dist/chunk-2MXOKJQO.js.map +1 -0
- package/dist/chunk-4JM32Z7J.js +51 -0
- package/dist/chunk-4JM32Z7J.js.map +1 -0
- package/dist/chunk-53Y6VLAW.js +132 -0
- package/dist/chunk-53Y6VLAW.js.map +1 -0
- package/dist/chunk-6V2VAUWP.js +24 -0
- package/dist/chunk-6V2VAUWP.js.map +1 -0
- package/dist/chunk-FQQMEM26.js +59 -0
- package/dist/chunk-FQQMEM26.js.map +1 -0
- package/dist/chunk-JYT6BTRE.js +298 -0
- package/dist/chunk-JYT6BTRE.js.map +1 -0
- package/dist/chunk-MFEOIJI7.js +127 -0
- package/dist/chunk-MFEOIJI7.js.map +1 -0
- package/dist/chunk-R5CUDFOI.js +61 -0
- package/dist/chunk-R5CUDFOI.js.map +1 -0
- package/dist/chunk-STHGZWBQ.js +1455 -0
- package/dist/chunk-STHGZWBQ.js.map +1 -0
- package/dist/{chunk-XV3KZRQX.js → chunk-STZYH32S.js} +9 -140
- package/dist/chunk-STZYH32S.js.map +1 -0
- package/dist/chunk-TDV3SFXT.js +75 -0
- package/dist/chunk-TDV3SFXT.js.map +1 -0
- package/dist/chunk-YUG6XQHJ.js +55 -0
- package/dist/chunk-YUG6XQHJ.js.map +1 -0
- package/dist/data/registry-routes.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +129 -3456
- package/dist/index.js.map +1 -1
- package/dist/settings/ShippingSettings.d.ts.map +1 -1
- package/dist/views/EmailTemplatePage.d.ts.map +1 -1
- package/dist/views/EmailsPage.d.ts.map +1 -1
- package/dist/views/OrderDeliveryPanel.d.ts +3 -1
- package/dist/views/OrderDeliveryPanel.d.ts.map +1 -1
- package/dist/views/OrderDetailView.d.ts.map +1 -1
- package/dist/views/ProductContentTab.d.ts.map +1 -1
- package/dist/views/ProductMarketPricesTab.d.ts.map +1 -1
- package/dist/views/media-links.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/ShopPage-NUIIWBCB.js.map +0 -1
- package/dist/chunk-XV3KZRQX.js.map +0 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { SettingsState } from './chunk-FQQMEM26.js';
|
|
2
|
+
import { useStoreSettings } from './chunk-R5CUDFOI.js';
|
|
3
|
+
import './chunk-YUG6XQHJ.js';
|
|
4
|
+
import './chunk-2MXOKJQO.js';
|
|
5
|
+
import { SettingsGroup, ToggleRow, SelectRow } from '@fayz-ai/admin';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var FIELDS = [
|
|
9
|
+
{ key: "name", label: "Nome completo", locked: true, hint: "Sempre pedido \u2014 \xE9 para quem a entrega vai." },
|
|
10
|
+
{ key: "email", label: "E-mail", locked: true, hint: "Sempre pedido \u2014 \xE9 por onde o pedido \xE9 confirmado." },
|
|
11
|
+
{ key: "phone", label: "Telefone", hint: "Vai junto com o endere\xE7o \u2014 \xE9 por onde o entregador liga." },
|
|
12
|
+
// Back, now that 0050 gives it a column. Deliberately NOT locked: a store that
|
|
13
|
+
// neither charges through a gateway nor issues an invoice has no use for it.
|
|
14
|
+
// The checkout escalates it to required on its own when a gateway is on —
|
|
15
|
+
// that is a fact about the charge, not a preference the merchant can waive.
|
|
16
|
+
{ key: "document", label: "CPF / CNPJ", hint: "Obrigat\xF3rio quando a loja cobra por gateway ou emite nota \u2014 nesse caso a vitrine j\xE1 pede sozinha." },
|
|
17
|
+
{ key: "postalCode", label: "CEP", locked: true, hint: "Sempre pedido \u2014 \xE9 o que cota o frete." },
|
|
18
|
+
{ key: "street", label: "Endere\xE7o", locked: true },
|
|
19
|
+
{ key: "number", label: "N\xFAmero", locked: true, hint: "Sem n\xFAmero o entregador n\xE3o chega." },
|
|
20
|
+
{ key: "complement", label: "Complemento" },
|
|
21
|
+
{ key: "district", label: "Bairro", locked: true },
|
|
22
|
+
{ key: "city", label: "Cidade", locked: true },
|
|
23
|
+
{ key: "state", label: "UF", locked: true }
|
|
24
|
+
];
|
|
25
|
+
var REQUIREMENT = [
|
|
26
|
+
{ value: "off", label: "N\xE3o pedir" },
|
|
27
|
+
{ value: "optional", label: "Pedir (opcional)" },
|
|
28
|
+
{ value: "required", label: "Pedir (obrigat\xF3rio)" }
|
|
29
|
+
];
|
|
30
|
+
function toRequirement(policy, locked) {
|
|
31
|
+
if (locked) return "required";
|
|
32
|
+
if (policy?.visible === false) return "off";
|
|
33
|
+
return policy?.required ? "required" : "optional";
|
|
34
|
+
}
|
|
35
|
+
function fromRequirement(choice) {
|
|
36
|
+
if (choice === "off") return { visible: false, required: false };
|
|
37
|
+
return { visible: true, required: choice === "required" };
|
|
38
|
+
}
|
|
39
|
+
function CheckoutSettings({ provider }) {
|
|
40
|
+
const settings = useStoreSettings(provider, {
|
|
41
|
+
guestCheckout: true,
|
|
42
|
+
requireConfirmedEmail: false,
|
|
43
|
+
marketingOptIn: false,
|
|
44
|
+
discountsEnabled: true
|
|
45
|
+
});
|
|
46
|
+
const { value, set } = settings;
|
|
47
|
+
const fields = value.checkoutFields ?? {};
|
|
48
|
+
const setField = (key, choice) => set({ checkoutFields: { ...fields, [key]: fromRequirement(choice) } });
|
|
49
|
+
return /* @__PURE__ */ jsxs(SettingsState, { ...settings, children: [
|
|
50
|
+
/* @__PURE__ */ jsxs(
|
|
51
|
+
SettingsGroup,
|
|
52
|
+
{
|
|
53
|
+
title: "Contas de cliente",
|
|
54
|
+
description: "Se o comprador precisa de uma conta antes de finalizar.",
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsx(
|
|
57
|
+
ToggleRow,
|
|
58
|
+
{
|
|
59
|
+
label: "Permitir compra sem cadastro",
|
|
60
|
+
description: "Desligado, o comprador precisa entrar ou criar conta. Esta \xE9 imposta no servidor: um pedido sem conta \xE9 recusado.",
|
|
61
|
+
checked: value.guestCheckout ?? true,
|
|
62
|
+
onChange: (v) => set({ guestCheckout: v })
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
/* @__PURE__ */ jsx(
|
|
66
|
+
ToggleRow,
|
|
67
|
+
{
|
|
68
|
+
label: "Exigir e-mail confirmado",
|
|
69
|
+
description: "O comprador recebe um link ao se cadastrar e s\xF3 finaliza depois de clicar. Vale na vitrine \u2014 confirmar e-mail \xE9 um fato da conta, n\xE3o do pedido.",
|
|
70
|
+
checked: value.requireConfirmedEmail ?? false,
|
|
71
|
+
onChange: (v) => set({ requireConfirmedEmail: v }),
|
|
72
|
+
disabled: value.guestCheckout !== false
|
|
73
|
+
}
|
|
74
|
+
),
|
|
75
|
+
value.guestCheckout !== false && /* @__PURE__ */ jsx("p", { className: "py-3 text-xs text-muted-foreground", children: "\xABExigir e-mail confirmado\xBB s\xF3 faz sentido com o cadastro obrigat\xF3rio: enquanto a compra sem cadastro est\xE1 liberada, n\xE3o h\xE1 conta cujo e-mail confirmar." })
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
/* @__PURE__ */ jsx(SettingsGroup, { title: "Carrinho", description: "O que o comprador pode aplicar antes de pagar.", children: /* @__PURE__ */ jsx(
|
|
80
|
+
ToggleRow,
|
|
81
|
+
{
|
|
82
|
+
label: "Cupons de desconto",
|
|
83
|
+
description: "Mostrar o campo de c\xF3digo no carrinho e no checkout.",
|
|
84
|
+
checked: value.discountsEnabled ?? true,
|
|
85
|
+
onChange: (v) => set({ discountsEnabled: v })
|
|
86
|
+
}
|
|
87
|
+
) }),
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
SettingsGroup,
|
|
90
|
+
{
|
|
91
|
+
title: "Consentimento de marketing",
|
|
92
|
+
description: "Coleta de opt-in para e-mails, no padr\xE3o que a LGPD pede: marcado por quem quer, nunca por padr\xE3o.",
|
|
93
|
+
children: /* @__PURE__ */ jsx(
|
|
94
|
+
ToggleRow,
|
|
95
|
+
{
|
|
96
|
+
label: "Mostrar caixa de opt-in",
|
|
97
|
+
description: "Exibir a caixa \xABquero receber novidades\xBB no checkout, desmarcada.",
|
|
98
|
+
checked: value.marketingOptIn ?? false,
|
|
99
|
+
onChange: (v) => set({ marketingOptIn: v })
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
/* @__PURE__ */ jsx(
|
|
105
|
+
SettingsGroup,
|
|
106
|
+
{
|
|
107
|
+
title: "Campos do cliente",
|
|
108
|
+
description: "O que o comprador v\xEA e o que ele precisa preencher. Campos travados s\xE3o os que a entrega n\xE3o dispensa.",
|
|
109
|
+
children: FIELDS.map((field) => /* @__PURE__ */ jsx(
|
|
110
|
+
SelectRow,
|
|
111
|
+
{
|
|
112
|
+
label: field.label,
|
|
113
|
+
description: field.hint,
|
|
114
|
+
value: toRequirement(fields[field.key], !!field.locked),
|
|
115
|
+
options: field.locked ? [{ value: "required", label: "Pedir (obrigat\xF3rio)" }] : REQUIREMENT,
|
|
116
|
+
onChange: (choice) => setField(field.key, choice),
|
|
117
|
+
disabled: field.locked
|
|
118
|
+
},
|
|
119
|
+
field.key
|
|
120
|
+
))
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
] });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { CheckoutSettings };
|
|
127
|
+
//# sourceMappingURL=CheckoutSettings-GQRYZF6I.js.map
|
|
128
|
+
//# sourceMappingURL=CheckoutSettings-GQRYZF6I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/settings/CheckoutSettings.tsx"],"names":[],"mappings":";;;;;;;AAqBA,IAAM,MAAA,GAA2F;AAAA,EAC/F,EAAE,KAAK,MAAA,EAAQ,KAAA,EAAO,iBAAiB,MAAA,EAAQ,IAAA,EAAM,MAAM,oDAAA,EAA6C;AAAA,EACxG,EAAE,KAAK,OAAA,EAAS,KAAA,EAAO,UAAU,MAAA,EAAQ,IAAA,EAAM,MAAM,8DAAA,EAAoD;AAAA,EACzG,EAAE,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,UAAA,EAAY,MAAM,qEAAA,EAA2D;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpG,EAAE,GAAA,EAAK,UAAA,EAAY,KAAA,EAAO,YAAA,EAAc,MAAM,8GAAA,EAAoG;AAAA,EAClJ,EAAE,KAAK,YAAA,EAAc,KAAA,EAAO,OAAO,MAAA,EAAQ,IAAA,EAAM,MAAM,+CAAA,EAAwC;AAAA,EAC/F,EAAE,GAAA,EAAK,QAAA,EAAU,KAAA,EAAO,aAAA,EAAY,QAAQ,IAAA,EAAK;AAAA,EACjD,EAAE,KAAK,QAAA,EAAU,KAAA,EAAO,aAAU,MAAA,EAAQ,IAAA,EAAM,MAAM,0CAAA,EAAqC;AAAA,EAC3F,EAAE,GAAA,EAAK,YAAA,EAAc,KAAA,EAAO,aAAA,EAAc;AAAA,EAC1C,EAAE,GAAA,EAAK,UAAA,EAAY,KAAA,EAAO,QAAA,EAAU,QAAQ,IAAA,EAAK;AAAA,EACjD,EAAE,GAAA,EAAK,MAAA,EAAQ,KAAA,EAAO,QAAA,EAAU,QAAQ,IAAA,EAAK;AAAA,EAC7C,EAAE,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA;AACvC,CAAA;AAEA,IAAM,WAAA,GAAc;AAAA,EAClB,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,cAAA,EAAY;AAAA,EACnC,EAAE,KAAA,EAAO,UAAA,EAAY,KAAA,EAAO,kBAAA,EAAmB;AAAA,EAC/C,EAAE,KAAA,EAAO,UAAA,EAAY,KAAA,EAAO,wBAAA;AAC9B,CAAA;AAEA,SAAS,aAAA,CAAc,QAAyC,MAAA,EAAyB;AACvF,EAAA,IAAI,QAAQ,OAAO,UAAA;AACnB,EAAA,IAAI,MAAA,EAAQ,OAAA,KAAY,KAAA,EAAO,OAAO,KAAA;AACtC,EAAA,OAAO,MAAA,EAAQ,WAAW,UAAA,GAAa,UAAA;AACzC;AAEA,SAAS,gBAAgB,MAAA,EAAqC;AAI5D,EAAA,IAAI,WAAW,KAAA,EAAO,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,UAAU,KAAA,EAAM;AAC/D,EAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,QAAA,EAAU,WAAW,UAAA,EAAW;AAC1D;AAYO,SAAS,gBAAA,CAAiB,EAAE,QAAA,EAAS,EAA0B;AACpE,EAAA,MAAM,QAAA,GAAW,iBAAiB,QAAA,EAAU;AAAA,IAC1C,aAAA,EAAe,IAAA;AAAA,IACf,qBAAA,EAAuB,KAAA;AAAA,IACvB,cAAA,EAAgB,KAAA;AAAA,IAChB,gBAAA,EAAkB;AAAA,GACnB,CAAA;AACD,EAAA,MAAM,EAAE,KAAA,EAAO,GAAA,EAAI,GAAI,QAAA;AACvB,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,cAAA,IAAkB,EAAC;AAExC,EAAA,MAAM,WAAW,CAAC,GAAA,EAAuB,MAAA,KACvC,GAAA,CAAI,EAAE,cAAA,EAAgB,EAAE,GAAG,MAAA,EAAQ,CAAC,GAAG,GAAG,gBAAgB,MAAM,CAAA,IAAK,CAAA;AAEvE,EAAA,uBACE,IAAA,CAAC,aAAA,EAAA,EAAe,GAAG,QAAA,EACjB,QAAA,EAAA;AAAA,oBAAA,IAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,mBAAA;AAAA,QACN,WAAA,EAAY,yDAAA;AAAA,QAEZ,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cACC,KAAA,EAAM,8BAAA;AAAA,cACN,WAAA,EAAY,yHAAA;AAAA,cACZ,OAAA,EAAS,MAAM,aAAA,IAAiB,IAAA;AAAA,cAChC,UAAU,CAAC,CAAA,KAAM,IAAI,EAAE,aAAA,EAAe,GAAG;AAAA;AAAA,WAC3C;AAAA,0BACA,GAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cACC,KAAA,EAAM,0BAAA;AAAA,cACN,WAAA,EAAY,gKAAA;AAAA,cACZ,OAAA,EAAS,MAAM,qBAAA,IAAyB,KAAA;AAAA,cACxC,UAAU,CAAC,CAAA,KAAM,IAAI,EAAE,qBAAA,EAAuB,GAAG,CAAA;AAAA,cACjD,QAAA,EAAU,MAAM,aAAA,KAAkB;AAAA;AAAA,WACpC;AAAA,UACC,MAAM,aAAA,KAAkB,KAAA,wBACtB,GAAA,EAAA,EAAE,SAAA,EAAU,sCAAqC,QAAA,EAAA,8KAAA,EAGlD;AAAA;AAAA;AAAA,KAEJ;AAAA,oBAEA,GAAA,CAAC,aAAA,EAAA,EAAc,KAAA,EAAM,UAAA,EAAW,aAAY,gDAAA,EAC1C,QAAA,kBAAA,GAAA;AAAA,MAAC,SAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,oBAAA;AAAA,QACN,WAAA,EAAY,yDAAA;AAAA,QACZ,OAAA,EAAS,MAAM,gBAAA,IAAoB,IAAA;AAAA,QACnC,UAAU,CAAC,CAAA,KAAM,IAAI,EAAE,gBAAA,EAAkB,GAAG;AAAA;AAAA,KAC9C,EACF,CAAA;AAAA,oBAEA,GAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,4BAAA;AAAA,QACN,WAAA,EAAY,0GAAA;AAAA,QAEZ,QAAA,kBAAA,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,yBAAA;AAAA,YACN,WAAA,EAAY,yEAAA;AAAA,YACZ,OAAA,EAAS,MAAM,cAAA,IAAkB,KAAA;AAAA,YACjC,UAAU,CAAC,CAAA,KAAM,IAAI,EAAE,cAAA,EAAgB,GAAG;AAAA;AAAA;AAC5C;AAAA,KACF;AAAA,oBAEA,GAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,mBAAA;AAAA,QACN,WAAA,EAAY,iHAAA;AAAA,QAEX,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,qBACX,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YAEC,OAAO,KAAA,CAAM,KAAA;AAAA,YACb,aAAa,KAAA,CAAM,IAAA;AAAA,YACnB,KAAA,EAAO,cAAc,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,EAAG,CAAC,CAAC,KAAA,CAAM,MAAM,CAAA;AAAA,YACtD,OAAA,EAAS,KAAA,CAAM,MAAA,GAAS,CAAC,EAAE,OAAO,UAAA,EAAY,KAAA,EAAO,wBAAA,EAAuB,CAAA,GAAI,WAAA;AAAA,YAChF,UAAU,CAAC,MAAA,KAAW,QAAA,CAAS,KAAA,CAAM,KAAK,MAAM,CAAA;AAAA,YAChD,UAAU,KAAA,CAAM;AAAA,WAAA;AAAA,UANX,KAAA,CAAM;AAAA,SAQd;AAAA;AAAA;AACH,GAAA,EACF,CAAA;AAEJ","file":"CheckoutSettings-GQRYZF6I.js","sourcesContent":["import React from 'react'\nimport { SettingsGroup, SelectRow, ToggleRow } from '@fayz-ai/admin'\nimport type { CheckoutFieldKey, CheckoutFieldPolicy } from '@fayz-ai/shop'\nimport type { ShopProviderResolver } from '../data/shared'\nimport { useStoreSettings } from './useStoreSettings'\nimport { SettingsState } from './SettingsState'\n\nexport interface CheckoutSettingsProps {\n provider?: ShopProviderResolver\n}\n\n/**\n * The fields the checkout form can ask for, and which of them a store may\n * actually turn off.\n *\n * `locked` is not a UI nicety — it is the delivery reality. A courier cannot\n * deliver to a street with no number and no district, and the checkout's own\n * validation refuses those today; offering a switch that promises otherwise\n * would be offering to break deliveries. The order those appear in is the order\n * of the form, so this list reads like the screen the shopper sees.\n */\nconst FIELDS: Array<{ key: CheckoutFieldKey; label: string; locked?: boolean; hint?: string }> = [\n { key: 'name', label: 'Nome completo', locked: true, hint: 'Sempre pedido — é para quem a entrega vai.' },\n { key: 'email', label: 'E-mail', locked: true, hint: 'Sempre pedido — é por onde o pedido é confirmado.' },\n { key: 'phone', label: 'Telefone', hint: 'Vai junto com o endereço — é por onde o entregador liga.' },\n // Back, now that 0050 gives it a column. Deliberately NOT locked: a store that\n // neither charges through a gateway nor issues an invoice has no use for it.\n // The checkout escalates it to required on its own when a gateway is on —\n // that is a fact about the charge, not a preference the merchant can waive.\n { key: 'document', label: 'CPF / CNPJ', hint: 'Obrigatório quando a loja cobra por gateway ou emite nota — nesse caso a vitrine já pede sozinha.' },\n { key: 'postalCode', label: 'CEP', locked: true, hint: 'Sempre pedido — é o que cota o frete.' },\n { key: 'street', label: 'Endereço', locked: true },\n { key: 'number', label: 'Número', locked: true, hint: 'Sem número o entregador não chega.' },\n { key: 'complement', label: 'Complemento' },\n { key: 'district', label: 'Bairro', locked: true },\n { key: 'city', label: 'Cidade', locked: true },\n { key: 'state', label: 'UF', locked: true },\n]\n\nconst REQUIREMENT = [\n { value: 'off', label: 'Não pedir' },\n { value: 'optional', label: 'Pedir (opcional)' },\n { value: 'required', label: 'Pedir (obrigatório)' },\n]\n\nfunction toRequirement(policy: CheckoutFieldPolicy | undefined, locked: boolean): string {\n if (locked) return 'required'\n if (policy?.visible === false) return 'off'\n return policy?.required ? 'required' : 'optional'\n}\n\nfunction fromRequirement(choice: string): CheckoutFieldPolicy {\n // 'off' writes both keys rather than only `visible`: a field nobody can see\n // cannot be required, and leaving a stale `required: true` behind is how a\n // hidden field later reappears as a blocking validation nobody can satisfy.\n if (choice === 'off') return { visible: false, required: false }\n return { visible: true, required: choice === 'required' }\n}\n\n/**\n * Checkout — quem pode comprar e o que é pedido no caminho.\n *\n * A honestidade sobre quem impõe o quê está nas descrições, e não é detalhe:\n * «compra sem cadastro» é recusada pelo SERVIDOR (trigger da 0045), então\n * desligá-la vale mesmo contra um POST direto no RPC. «Exigir e-mail\n * confirmado» e os campos do formulário são de INTERFACE — a vitrine obedece, e\n * um comprador determinado não é impedido pelo banco. Prometer imposição onde\n * ela não existe é pior do que não ter a opção.\n */\nexport function CheckoutSettings({ provider }: CheckoutSettingsProps) {\n const settings = useStoreSettings(provider, {\n guestCheckout: true,\n requireConfirmedEmail: false,\n marketingOptIn: false,\n discountsEnabled: true,\n })\n const { value, set } = settings\n const fields = value.checkoutFields ?? {}\n\n const setField = (key: CheckoutFieldKey, choice: string) =>\n set({ checkoutFields: { ...fields, [key]: fromRequirement(choice) } })\n\n return (\n <SettingsState {...settings}>\n <SettingsGroup\n title=\"Contas de cliente\"\n description=\"Se o comprador precisa de uma conta antes de finalizar.\"\n >\n <ToggleRow\n label=\"Permitir compra sem cadastro\"\n description=\"Desligado, o comprador precisa entrar ou criar conta. Esta é imposta no servidor: um pedido sem conta é recusado.\"\n checked={value.guestCheckout ?? true}\n onChange={(v) => set({ guestCheckout: v })}\n />\n <ToggleRow\n label=\"Exigir e-mail confirmado\"\n description=\"O comprador recebe um link ao se cadastrar e só finaliza depois de clicar. Vale na vitrine — confirmar e-mail é um fato da conta, não do pedido.\"\n checked={value.requireConfirmedEmail ?? false}\n onChange={(v) => set({ requireConfirmedEmail: v })}\n disabled={value.guestCheckout !== false}\n />\n {value.guestCheckout !== false && (\n <p className=\"py-3 text-xs text-muted-foreground\">\n «Exigir e-mail confirmado» só faz sentido com o cadastro obrigatório: enquanto a compra\n sem cadastro está liberada, não há conta cujo e-mail confirmar.\n </p>\n )}\n </SettingsGroup>\n\n <SettingsGroup title=\"Carrinho\" description=\"O que o comprador pode aplicar antes de pagar.\">\n <ToggleRow\n label=\"Cupons de desconto\"\n description=\"Mostrar o campo de código no carrinho e no checkout.\"\n checked={value.discountsEnabled ?? true}\n onChange={(v) => set({ discountsEnabled: v })}\n />\n </SettingsGroup>\n\n <SettingsGroup\n title=\"Consentimento de marketing\"\n description=\"Coleta de opt-in para e-mails, no padrão que a LGPD pede: marcado por quem quer, nunca por padrão.\"\n >\n <ToggleRow\n label=\"Mostrar caixa de opt-in\"\n description=\"Exibir a caixa «quero receber novidades» no checkout, desmarcada.\"\n checked={value.marketingOptIn ?? false}\n onChange={(v) => set({ marketingOptIn: v })}\n />\n </SettingsGroup>\n\n <SettingsGroup\n title=\"Campos do cliente\"\n description=\"O que o comprador vê e o que ele precisa preencher. Campos travados são os que a entrega não dispensa.\"\n >\n {FIELDS.map((field) => (\n <SelectRow\n key={field.key}\n label={field.label}\n description={field.hint}\n value={toRequirement(fields[field.key], !!field.locked)}\n options={field.locked ? [{ value: 'required', label: 'Pedir (obrigatório)' }] : REQUIREMENT}\n onChange={(choice) => setField(field.key, choice)}\n disabled={field.locked}\n />\n ))}\n </SettingsGroup>\n </SettingsState>\n )\n}\n"]}
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Upload, ImageOff, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Package, Film, FileText } from 'lucide-react';
|
|
3
|
+
import { PageTabs, PageHeaderActions, Card, ConfirmDialog, PageHeader, Button, toast, Hint } from '@fayz-ai/ui';
|
|
4
|
+
import { PageActions, AskAIButton } from '@fayz-ai/admin';
|
|
5
|
+
import { productImageToMediaFile, getShopProvider } from '@fayz-ai/shop';
|
|
6
|
+
import { appHref } from '@fayz-ai/core';
|
|
7
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
// src/views/ContentPage.tsx
|
|
10
|
+
function recordRoute(reference) {
|
|
11
|
+
return appHref(`/products/${reference.id}/images`);
|
|
12
|
+
}
|
|
13
|
+
function downloadUrlOf(file) {
|
|
14
|
+
if (!/^https?:/i.test(file.url)) return file.url;
|
|
15
|
+
const separator = file.url.includes("?") ? "&" : "?";
|
|
16
|
+
return `${file.url}${separator}download=${encodeURIComponent(file.name)}`;
|
|
17
|
+
}
|
|
18
|
+
var TABS = [
|
|
19
|
+
{ value: "all", label: "Todos" },
|
|
20
|
+
{ value: "image", label: "Imagens" },
|
|
21
|
+
{ value: "video", label: "V\xEDdeos" }
|
|
22
|
+
];
|
|
23
|
+
var LOW_RESOLUTION_PX = 800;
|
|
24
|
+
function resolve(resolver) {
|
|
25
|
+
return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
|
|
26
|
+
}
|
|
27
|
+
function formatSize(bytes) {
|
|
28
|
+
if (!bytes) return "";
|
|
29
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
30
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
31
|
+
return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
32
|
+
}
|
|
33
|
+
function ContentPage({ resolveProvider }) {
|
|
34
|
+
const provider = React.useMemo(() => resolve(resolveProvider), [resolveProvider]);
|
|
35
|
+
const supported = typeof provider.listMediaFiles === "function";
|
|
36
|
+
const [files, setFiles] = React.useState(null);
|
|
37
|
+
const [tab, setTab] = React.useState("all");
|
|
38
|
+
const [uploading, setUploading] = React.useState(false);
|
|
39
|
+
const [pendingDelete, setPendingDelete] = React.useState(null);
|
|
40
|
+
const [deleting, setDeleting] = React.useState(false);
|
|
41
|
+
const [replacing, setReplacing] = React.useState(false);
|
|
42
|
+
const [openId, setOpenId] = React.useState(null);
|
|
43
|
+
const [dimensions, setDimensions] = React.useState({});
|
|
44
|
+
const uploadInputRef = React.useRef(null);
|
|
45
|
+
const reload = React.useCallback(async () => {
|
|
46
|
+
if (!provider.listMediaFiles) {
|
|
47
|
+
setFiles([]);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
setFiles(await provider.listMediaFiles());
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.warn("[content] failed to list media", err);
|
|
54
|
+
setFiles([]);
|
|
55
|
+
}
|
|
56
|
+
}, [provider]);
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
void reload();
|
|
59
|
+
}, [reload]);
|
|
60
|
+
const measure = React.useCallback((id, d) => {
|
|
61
|
+
setDimensions((prev) => prev[id] ? prev : { ...prev, [id]: d });
|
|
62
|
+
}, []);
|
|
63
|
+
const handleUpload = async (picked) => {
|
|
64
|
+
if (!picked?.length || !provider.uploadMediaFile) return;
|
|
65
|
+
setUploading(true);
|
|
66
|
+
try {
|
|
67
|
+
for (const file of Array.from(picked)) {
|
|
68
|
+
await provider.uploadMediaFile(file);
|
|
69
|
+
}
|
|
70
|
+
toast.success(picked.length === 1 ? "Arquivo enviado" : `${picked.length} arquivos enviados`);
|
|
71
|
+
await reload();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.warn("[content] upload failed", err);
|
|
74
|
+
toast.error("N\xE3o foi poss\xEDvel enviar o arquivo");
|
|
75
|
+
} finally {
|
|
76
|
+
setUploading(false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const handleReplace = async (file, picked) => {
|
|
80
|
+
setReplacing(true);
|
|
81
|
+
try {
|
|
82
|
+
let nextId;
|
|
83
|
+
if (file.reference) {
|
|
84
|
+
if (!provider.replaceProductImage) throw new Error("unsupported");
|
|
85
|
+
const image = await provider.replaceProductImage(file.reference.attachmentId, picked);
|
|
86
|
+
nextId = productImageToMediaFile(image, file.reference.label).id;
|
|
87
|
+
} else {
|
|
88
|
+
if (!provider.uploadMediaFile) throw new Error("unsupported");
|
|
89
|
+
const uploaded = await provider.uploadMediaFile(picked);
|
|
90
|
+
if (provider.deleteMediaFile) await provider.deleteMediaFile(file.id);
|
|
91
|
+
nextId = uploaded.id;
|
|
92
|
+
}
|
|
93
|
+
await reload();
|
|
94
|
+
setOpenId(nextId);
|
|
95
|
+
toast.success("Arquivo trocado");
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.warn("[content] replace failed", err);
|
|
98
|
+
toast.error("N\xE3o foi poss\xEDvel trocar o arquivo");
|
|
99
|
+
} finally {
|
|
100
|
+
setReplacing(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const confirmDelete = async () => {
|
|
104
|
+
const file = pendingDelete;
|
|
105
|
+
if (!file) return;
|
|
106
|
+
setDeleting(true);
|
|
107
|
+
try {
|
|
108
|
+
if (file.reference) {
|
|
109
|
+
await provider.deleteProductImage(file.reference.attachmentId);
|
|
110
|
+
} else if (provider.deleteMediaFile) {
|
|
111
|
+
await provider.deleteMediaFile(file.id);
|
|
112
|
+
}
|
|
113
|
+
setFiles((prev) => prev?.filter((f) => f.id !== file.id) ?? null);
|
|
114
|
+
setPendingDelete(null);
|
|
115
|
+
if (openId === file.id) setOpenId(null);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.warn("[content] delete failed", err);
|
|
118
|
+
toast.error("N\xE3o foi poss\xEDvel excluir o arquivo");
|
|
119
|
+
} finally {
|
|
120
|
+
setDeleting(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const visible = (files ?? []).filter((f) => tab === "all" || f.kind === tab);
|
|
124
|
+
const openIndex = visible.findIndex((f) => f.id === openId);
|
|
125
|
+
const open = openIndex >= 0 ? visible[openIndex] : null;
|
|
126
|
+
const canDelete = (file) => Boolean(file.reference) || typeof provider.deleteMediaFile === "function";
|
|
127
|
+
const canReplace = (file) => file.reference ? typeof provider.replaceProductImage === "function" : typeof provider.uploadMediaFile === "function";
|
|
128
|
+
if (open) {
|
|
129
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
130
|
+
/* @__PURE__ */ jsx(
|
|
131
|
+
MediaDetail,
|
|
132
|
+
{
|
|
133
|
+
file: open,
|
|
134
|
+
dimensions: dimensions[open.id],
|
|
135
|
+
onMeasured: (d) => measure(open.id, d),
|
|
136
|
+
position: { index: openIndex, total: visible.length },
|
|
137
|
+
onNavigate: (delta) => setOpenId(visible[openIndex + delta]?.id ?? open.id),
|
|
138
|
+
onBack: () => setOpenId(null),
|
|
139
|
+
onReplace: canReplace(open) ? (picked) => void handleReplace(open, picked) : void 0,
|
|
140
|
+
onDelete: canDelete(open) ? () => setPendingDelete(open) : void 0,
|
|
141
|
+
replacing
|
|
142
|
+
}
|
|
143
|
+
),
|
|
144
|
+
/* @__PURE__ */ jsx(
|
|
145
|
+
DeleteConfirm,
|
|
146
|
+
{
|
|
147
|
+
file: pendingDelete,
|
|
148
|
+
loading: deleting,
|
|
149
|
+
onConfirm: () => void confirmDelete(),
|
|
150
|
+
onCancel: () => setPendingDelete(null)
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
] });
|
|
154
|
+
}
|
|
155
|
+
const attached = visible.filter((f) => f.reference);
|
|
156
|
+
const loose = visible.filter((f) => !f.reference);
|
|
157
|
+
const countOf = (t) => {
|
|
158
|
+
if (files === null) return void 0;
|
|
159
|
+
if (t === "all") return files.length;
|
|
160
|
+
return files.filter((f) => f.kind === t).length;
|
|
161
|
+
};
|
|
162
|
+
const tabs = TABS.map((t) => ({ ...t, count: countOf(t.value) }));
|
|
163
|
+
const uploadActions = [{
|
|
164
|
+
id: "upload",
|
|
165
|
+
label: uploading ? "Enviando\u2026" : "Enviar arquivos",
|
|
166
|
+
icon: Upload,
|
|
167
|
+
intent: "primary",
|
|
168
|
+
// It puts a new file in the library — the FAB is a "+", not a "⋮".
|
|
169
|
+
creates: true,
|
|
170
|
+
disabled: uploading,
|
|
171
|
+
onSelect: () => uploadInputRef.current?.click()
|
|
172
|
+
}];
|
|
173
|
+
const renderGrid = (list) => /* @__PURE__ */ jsx("div", { className: "fz-cards gap-4", children: list.map((file) => /* @__PURE__ */ jsx(
|
|
174
|
+
MediaCard,
|
|
175
|
+
{
|
|
176
|
+
file,
|
|
177
|
+
dimensions: dimensions[file.id],
|
|
178
|
+
onMeasured: (d) => measure(file.id, d),
|
|
179
|
+
onOpen: () => setOpenId(file.id),
|
|
180
|
+
onDelete: canDelete(file) ? () => setPendingDelete(file) : void 0
|
|
181
|
+
},
|
|
182
|
+
file.id
|
|
183
|
+
)) });
|
|
184
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
185
|
+
/* @__PURE__ */ jsx(
|
|
186
|
+
PageTabs,
|
|
187
|
+
{
|
|
188
|
+
tabs,
|
|
189
|
+
value: tab,
|
|
190
|
+
onChange: setTab,
|
|
191
|
+
"aria-label": "Arquivos"
|
|
192
|
+
}
|
|
193
|
+
),
|
|
194
|
+
supported && provider.uploadMediaFile && /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PageActions, { actions: uploadActions }) }),
|
|
195
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
196
|
+
!supported ? /* @__PURE__ */ jsxs(Card, { className: "p-12 text-center", children: [
|
|
197
|
+
/* @__PURE__ */ jsx(ImageOff, { className: "mx-auto h-8 w-8 text-muted-foreground" }),
|
|
198
|
+
/* @__PURE__ */ jsx("p", { className: "mt-3 font-medium", children: "Armazenamento n\xE3o configurado" }),
|
|
199
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "A biblioteca de arquivos precisa do storage da loja, que n\xE3o est\xE1 dispon\xEDvel neste modo." })
|
|
200
|
+
] }) : files === null ? /* @__PURE__ */ jsx("div", { className: "fz-cards gap-4", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsxs(Card, { className: "p-3", children: [
|
|
201
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-square animate-pulse rounded-md bg-muted" }),
|
|
202
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 h-3.5 w-2/3 animate-pulse rounded bg-muted" })
|
|
203
|
+
] }, i)) }) : visible.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "p-12 text-center", children: [
|
|
204
|
+
/* @__PURE__ */ jsx(ImageOff, { className: "mx-auto h-8 w-8 text-muted-foreground" }),
|
|
205
|
+
/* @__PURE__ */ jsx("p", { className: "mt-3 font-medium", children: "Nenhum arquivo ainda" }),
|
|
206
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "As fotos dos seus produtos e o que voc\xEA enviar aqui aparecem nesta biblioteca." })
|
|
207
|
+
] }) : attached.length && loose.length ? (
|
|
208
|
+
// Só separa quando há os dois: um título sozinho em cima de tudo é
|
|
209
|
+
// barulho, e a etiqueta em cada cartão já diz de quem é a foto.
|
|
210
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
211
|
+
/* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
212
|
+
/* @__PURE__ */ jsx(SectionTitle, { title: "Fotos de produtos", count: attached.length }),
|
|
213
|
+
renderGrid(attached)
|
|
214
|
+
] }),
|
|
215
|
+
/* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
216
|
+
/* @__PURE__ */ jsx(SectionTitle, { title: "Biblioteca", count: loose.length }),
|
|
217
|
+
renderGrid(loose)
|
|
218
|
+
] })
|
|
219
|
+
] })
|
|
220
|
+
) : renderGrid(visible),
|
|
221
|
+
/* @__PURE__ */ jsx(
|
|
222
|
+
DeleteConfirm,
|
|
223
|
+
{
|
|
224
|
+
file: pendingDelete,
|
|
225
|
+
loading: deleting,
|
|
226
|
+
onConfirm: () => void confirmDelete(),
|
|
227
|
+
onCancel: () => setPendingDelete(null)
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
supported && provider.uploadMediaFile && /* @__PURE__ */ jsx(
|
|
231
|
+
"input",
|
|
232
|
+
{
|
|
233
|
+
ref: uploadInputRef,
|
|
234
|
+
type: "file",
|
|
235
|
+
multiple: true,
|
|
236
|
+
className: "hidden",
|
|
237
|
+
onChange: (e) => {
|
|
238
|
+
void handleUpload(e.target.files).finally(() => {
|
|
239
|
+
if (uploadInputRef.current) uploadInputRef.current.value = "";
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
] })
|
|
245
|
+
] });
|
|
246
|
+
}
|
|
247
|
+
function DeleteConfirm({ file, loading, onConfirm, onCancel }) {
|
|
248
|
+
return /* @__PURE__ */ jsx(
|
|
249
|
+
ConfirmDialog,
|
|
250
|
+
{
|
|
251
|
+
open: file !== null,
|
|
252
|
+
loading,
|
|
253
|
+
variant: "destructive",
|
|
254
|
+
title: file?.reference ? "Excluir a foto do produto?" : "Excluir arquivo?",
|
|
255
|
+
description: file?.reference ? `A foto sai de "${file.reference.label}" e da loja imediatamente.` : "O arquivo sai da biblioteca. Se ele estiver em uso em algum lugar, o endere\xE7o para de funcionar.",
|
|
256
|
+
confirmLabel: "Excluir",
|
|
257
|
+
cancelLabel: "Cancelar",
|
|
258
|
+
onConfirm,
|
|
259
|
+
onCancel
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
function SectionTitle({ title, count }) {
|
|
264
|
+
return /* @__PURE__ */ jsxs("h2", { className: "text-sm font-semibold", children: [
|
|
265
|
+
title,
|
|
266
|
+
" ",
|
|
267
|
+
/* @__PURE__ */ jsxs("span", { className: "font-normal text-muted-foreground", children: [
|
|
268
|
+
"(",
|
|
269
|
+
count,
|
|
270
|
+
")"
|
|
271
|
+
] })
|
|
272
|
+
] });
|
|
273
|
+
}
|
|
274
|
+
function KindIcon({ kind, className }) {
|
|
275
|
+
const Icon = kind === "video" ? Film : FileText;
|
|
276
|
+
return /* @__PURE__ */ jsx(Icon, { className });
|
|
277
|
+
}
|
|
278
|
+
function MediaCard({ file, dimensions, onMeasured, onOpen, onDelete }) {
|
|
279
|
+
return /* @__PURE__ */ jsxs(Card, { className: "group relative overflow-hidden p-3", children: [
|
|
280
|
+
/* @__PURE__ */ jsxs(
|
|
281
|
+
Button,
|
|
282
|
+
{
|
|
283
|
+
variant: "bare",
|
|
284
|
+
size: "bare",
|
|
285
|
+
type: "button",
|
|
286
|
+
onClick: onOpen,
|
|
287
|
+
"aria-label": `Abrir ${file.name}`,
|
|
288
|
+
className: "font-normal gap-0 whitespace-normal relative flex aspect-square w-full items-center justify-center overflow-hidden rounded-md bg-muted",
|
|
289
|
+
children: [
|
|
290
|
+
file.kind === "image" ? /* @__PURE__ */ jsx(
|
|
291
|
+
"img",
|
|
292
|
+
{
|
|
293
|
+
src: file.url,
|
|
294
|
+
alt: file.name,
|
|
295
|
+
loading: "lazy",
|
|
296
|
+
className: "h-full w-full object-contain",
|
|
297
|
+
onLoad: (e) => {
|
|
298
|
+
const img = e.currentTarget;
|
|
299
|
+
if (img.naturalWidth) onMeasured({ width: img.naturalWidth, height: img.naturalHeight });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
) : /* @__PURE__ */ jsx(KindIcon, { kind: file.kind, className: "h-8 w-8 text-muted-foreground" }),
|
|
303
|
+
file.reference?.isCover && /* @__PURE__ */ jsx("span", { className: "absolute left-1.5 top-1.5 rounded-full bg-primary px-2 py-0.5 text-[11px] font-bold text-primary-foreground", children: "Capa" })
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
),
|
|
307
|
+
onDelete && /* @__PURE__ */ jsx(Hint, { label: "Excluir", children: /* @__PURE__ */ jsx(
|
|
308
|
+
Button,
|
|
309
|
+
{
|
|
310
|
+
variant: "bare",
|
|
311
|
+
size: "bare",
|
|
312
|
+
type: "button",
|
|
313
|
+
"aria-label": "Excluir",
|
|
314
|
+
onClick: onDelete,
|
|
315
|
+
className: "absolute right-4 top-4 rounded-md bg-card/90 p-1.5 text-muted-foreground opacity-0 shadow-button transition-opacity hover:text-destructive group-hover:opacity-100",
|
|
316
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
317
|
+
}
|
|
318
|
+
) }),
|
|
319
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 truncate text-[13px] font-medium", title: file.name, children: file.name }),
|
|
320
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: /* @__PURE__ */ jsx(FileMeta, { file, dimensions }) }),
|
|
321
|
+
isLowResolution(file, dimensions) && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] font-medium text-warning", children: "Baixa resolu\xE7\xE3o" }),
|
|
322
|
+
file.reference ? /* @__PURE__ */ jsx(RecordLink, { reference: file.reference, className: "mt-1.5 text-[11px]" }) : /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[11px] text-muted-foreground", children: "Biblioteca" })
|
|
323
|
+
] });
|
|
324
|
+
}
|
|
325
|
+
function MediaDetail({
|
|
326
|
+
file,
|
|
327
|
+
dimensions,
|
|
328
|
+
onMeasured,
|
|
329
|
+
position,
|
|
330
|
+
onNavigate,
|
|
331
|
+
onBack,
|
|
332
|
+
onReplace,
|
|
333
|
+
onDelete,
|
|
334
|
+
replacing
|
|
335
|
+
}) {
|
|
336
|
+
const replaceRef = React.useRef(null);
|
|
337
|
+
React.useEffect(() => {
|
|
338
|
+
function onKey(e) {
|
|
339
|
+
if (e.key === "ArrowLeft") onNavigate(-1);
|
|
340
|
+
if (e.key === "ArrowRight") onNavigate(1);
|
|
341
|
+
}
|
|
342
|
+
document.addEventListener("keydown", onKey);
|
|
343
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
344
|
+
}, [onNavigate]);
|
|
345
|
+
return (
|
|
346
|
+
// `fz-container` é o ponto de MEDIÇÃO: sem ele a container query de
|
|
347
|
+
// `fz-split` nunca casa e as duas colunas ficam empilhadas para sempre,
|
|
348
|
+
// por mais larga que a janela esteja.
|
|
349
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
|
|
350
|
+
/* @__PURE__ */ jsx(PageHeader, { title: file.name, parentLabel: "Conte\xFAdo", onBack }),
|
|
351
|
+
position.total > 1 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
352
|
+
/* @__PURE__ */ jsx(
|
|
353
|
+
Button,
|
|
354
|
+
{
|
|
355
|
+
variant: "bare",
|
|
356
|
+
size: "bare",
|
|
357
|
+
type: "button",
|
|
358
|
+
"aria-label": "Arquivo anterior",
|
|
359
|
+
disabled: position.index === 0,
|
|
360
|
+
onClick: () => onNavigate(-1),
|
|
361
|
+
className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
|
|
362
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
363
|
+
}
|
|
364
|
+
),
|
|
365
|
+
/* @__PURE__ */ jsxs("span", { className: "px-1 tabular-nums", children: [
|
|
366
|
+
position.index + 1,
|
|
367
|
+
" de ",
|
|
368
|
+
position.total
|
|
369
|
+
] }),
|
|
370
|
+
/* @__PURE__ */ jsx(
|
|
371
|
+
Button,
|
|
372
|
+
{
|
|
373
|
+
variant: "bare",
|
|
374
|
+
size: "bare",
|
|
375
|
+
type: "button",
|
|
376
|
+
"aria-label": "Pr\xF3ximo arquivo",
|
|
377
|
+
disabled: position.index === position.total - 1,
|
|
378
|
+
onClick: () => onNavigate(1),
|
|
379
|
+
className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
|
|
380
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
] }) }),
|
|
384
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
|
|
385
|
+
/* @__PURE__ */ jsx(Card, { className: "flex min-h-[320px] items-center justify-center overflow-hidden bg-muted p-2", children: file.kind === "image" ? /* @__PURE__ */ jsx(
|
|
386
|
+
"img",
|
|
387
|
+
{
|
|
388
|
+
src: file.url,
|
|
389
|
+
alt: file.name,
|
|
390
|
+
className: "max-h-[70vh] w-auto max-w-full object-contain",
|
|
391
|
+
onLoad: (e) => {
|
|
392
|
+
const img = e.currentTarget;
|
|
393
|
+
if (img.naturalWidth) onMeasured({ width: img.naturalWidth, height: img.naturalHeight });
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
file.url
|
|
397
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 p-12 text-muted-foreground", children: [
|
|
398
|
+
/* @__PURE__ */ jsx(KindIcon, { kind: file.kind, className: "h-10 w-10" }),
|
|
399
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: "Pr\xE9-visualiza\xE7\xE3o n\xE3o dispon\xEDvel" })
|
|
400
|
+
] }) }),
|
|
401
|
+
/* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
|
|
402
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
403
|
+
/* @__PURE__ */ jsx("p", { className: "break-words font-medium", title: file.name, children: file.name }),
|
|
404
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: /* @__PURE__ */ jsx(FileMeta, { file, dimensions }) }),
|
|
405
|
+
isLowResolution(file, dimensions) && /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded-md bg-warning/10 px-2 py-1.5 text-xs font-medium text-warning", children: [
|
|
406
|
+
"Baixa resolu\xE7\xE3o \u2014 no zoom da loja essa foto fica borrada. Troque por uma de pelo menos ",
|
|
407
|
+
LOW_RESOLUTION_PX,
|
|
408
|
+
"px no lado maior."
|
|
409
|
+
] })
|
|
410
|
+
] }),
|
|
411
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
412
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Onde \xE9 usada" }),
|
|
413
|
+
file.reference ? /* @__PURE__ */ jsx(RecordLink, { reference: file.reference, className: "mt-1 text-sm" }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Biblioteca \u2014 este arquivo n\xE3o est\xE1 preso a nenhum registro." }),
|
|
414
|
+
file.reference?.isCover && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\xC9 a capa: \xE9 ela que aparece no cat\xE1logo, no carrinho e no pedido." })
|
|
415
|
+
] }),
|
|
416
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
417
|
+
file.kind === "image" && /* @__PURE__ */ jsx(
|
|
418
|
+
AskAIButton,
|
|
419
|
+
{
|
|
420
|
+
className: "w-full",
|
|
421
|
+
size: "lg",
|
|
422
|
+
label: "Melhorar com IA",
|
|
423
|
+
media: { url: file.url, name: file.name },
|
|
424
|
+
draft: "Melhora essa imagem",
|
|
425
|
+
hint: "o que voc\xEA quer mudar nessa foto",
|
|
426
|
+
intro: {
|
|
427
|
+
persona: "Est\xFAdio de imagem",
|
|
428
|
+
pitch: `Vamos melhorar ${file.name}.`,
|
|
429
|
+
description: "Posso limpar o fundo, corrigir luz e cor, e gerar uma vers\xE3o em resolu\xE7\xE3o maior para o cat\xE1logo.",
|
|
430
|
+
suggestions: [
|
|
431
|
+
{ label: "Fundo branco", prompt: "Remove o fundo e deixa branco" },
|
|
432
|
+
{ label: "Mais n\xEDtida", prompt: "Aumenta a resolu\xE7\xE3o e a nitidez" },
|
|
433
|
+
{ label: "Corrigir a luz", prompt: "Corrige a ilumina\xE7\xE3o e as cores" }
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
),
|
|
438
|
+
/* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", className: "w-full", children: /* @__PURE__ */ jsxs("a", { href: downloadUrlOf(file), download: file.name, children: [
|
|
439
|
+
/* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }),
|
|
440
|
+
"Baixar"
|
|
441
|
+
] }) }),
|
|
442
|
+
onReplace && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
443
|
+
/* @__PURE__ */ jsx(
|
|
444
|
+
"input",
|
|
445
|
+
{
|
|
446
|
+
ref: replaceRef,
|
|
447
|
+
type: "file",
|
|
448
|
+
accept: file.kind === "image" ? "image/*" : void 0,
|
|
449
|
+
className: "hidden",
|
|
450
|
+
onChange: (e) => {
|
|
451
|
+
const picked = e.target.files?.[0];
|
|
452
|
+
e.target.value = "";
|
|
453
|
+
if (picked) onReplace(picked);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
),
|
|
457
|
+
/* @__PURE__ */ jsxs(
|
|
458
|
+
Button,
|
|
459
|
+
{
|
|
460
|
+
variant: "outline",
|
|
461
|
+
className: "w-full",
|
|
462
|
+
disabled: replacing,
|
|
463
|
+
onClick: () => replaceRef.current?.click(),
|
|
464
|
+
children: [
|
|
465
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${replacing ? "animate-spin" : ""}` }),
|
|
466
|
+
replacing ? "Trocando\u2026" : "Trocar arquivo"
|
|
467
|
+
]
|
|
468
|
+
}
|
|
469
|
+
),
|
|
470
|
+
file.reference && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "A troca mant\xE9m a posi\xE7\xE3o da foto na galeria do produto." })
|
|
471
|
+
] }),
|
|
472
|
+
onDelete && /* @__PURE__ */ jsxs(Button, { variant: "ghost", className: "w-full text-destructive hover:text-destructive", onClick: onDelete, children: [
|
|
473
|
+
/* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
|
|
474
|
+
"Excluir"
|
|
475
|
+
] })
|
|
476
|
+
] })
|
|
477
|
+
] })
|
|
478
|
+
] })
|
|
479
|
+
] })
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
function RecordLink({ reference, className = "" }) {
|
|
483
|
+
return /* @__PURE__ */ jsxs(
|
|
484
|
+
"a",
|
|
485
|
+
{
|
|
486
|
+
href: recordRoute(reference),
|
|
487
|
+
title: `Abrir ${reference.label}`,
|
|
488
|
+
className: `flex items-center gap-1 truncate font-medium text-primary hover:underline ${className}`,
|
|
489
|
+
children: [
|
|
490
|
+
/* @__PURE__ */ jsx(Package, { className: "h-3 w-3 shrink-0" }),
|
|
491
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: reference.label })
|
|
492
|
+
]
|
|
493
|
+
}
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
function FileMeta({ file, dimensions }) {
|
|
497
|
+
const parts = [
|
|
498
|
+
file.extension,
|
|
499
|
+
dimensions ? `${dimensions.width}\xD7${dimensions.height}` : "",
|
|
500
|
+
formatSize(file.size)
|
|
501
|
+
].filter(Boolean);
|
|
502
|
+
return /* @__PURE__ */ jsx(Fragment, { children: parts.join(" \xB7 ") });
|
|
503
|
+
}
|
|
504
|
+
function isLowResolution(file, dimensions) {
|
|
505
|
+
return file.kind === "image" && !!dimensions && Math.max(dimensions.width, dimensions.height) < LOW_RESOLUTION_PX;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export { ContentPage };
|
|
509
|
+
//# sourceMappingURL=ContentPage-2PHOZSFZ.js.map
|
|
510
|
+
//# sourceMappingURL=ContentPage-2PHOZSFZ.js.map
|