@fayz-ai/admin 0.62.0 → 0.63.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.
Files changed (169) hide show
  1. package/dist/{CommandPalette-B5PMQFDG.js → CommandPalette-XDJJ5E3O.js} +3 -3
  2. package/dist/{CommandPalette-B5PMQFDG.js.map → CommandPalette-XDJJ5E3O.js.map} +1 -1
  3. package/dist/ConnectedBrandingSettings-43ZM3HCI.js +680 -0
  4. package/dist/ConnectedBrandingSettings-43ZM3HCI.js.map +1 -0
  5. package/dist/ConnectedCompanySettings-I4MALI2Q.js +197 -0
  6. package/dist/ConnectedCompanySettings-I4MALI2Q.js.map +1 -0
  7. package/dist/ConnectedModulesSettings-A2VJ5BMJ.js +563 -0
  8. package/dist/ConnectedModulesSettings-A2VJ5BMJ.js.map +1 -0
  9. package/dist/ConnectedSecuritySettings-25AHA4HJ.js +189 -0
  10. package/dist/ConnectedSecuritySettings-25AHA4HJ.js.map +1 -0
  11. package/dist/ConnectedUserProfile-E7USJRUE.js +424 -0
  12. package/dist/ConnectedUserProfile-E7USJRUE.js.map +1 -0
  13. package/dist/FieldRulesEditor-YGIPKI5T.js +129 -0
  14. package/dist/FieldRulesEditor-YGIPKI5T.js.map +1 -0
  15. package/dist/GettingStartedWidget-HMIJ3Y24.js +12 -0
  16. package/dist/GettingStartedWidget-HMIJ3Y24.js.map +1 -0
  17. package/dist/PermissionProfilesTab-3ZPRCHMZ.js +548 -0
  18. package/dist/PermissionProfilesTab-3ZPRCHMZ.js.map +1 -0
  19. package/dist/PersonTimeline-HQZT527S.js +3 -0
  20. package/dist/{PersonTimeline-X7JTPUWJ.js.map → PersonTimeline-HQZT527S.js.map} +1 -1
  21. package/dist/ProjectsSettings-7O2F27MD.js +5 -0
  22. package/dist/ProjectsSettings-7O2F27MD.js.map +1 -0
  23. package/dist/ScheduleEditor-54ELWR45.js +8 -0
  24. package/dist/{ScheduleEditor-L2UJXKMY.js.map → ScheduleEditor-54ELWR45.js.map} +1 -1
  25. package/dist/SeuSiteWidget-QLSUVC63.js +6 -0
  26. package/dist/SeuSiteWidget-QLSUVC63.js.map +1 -0
  27. package/dist/SubscriptionPage-5GE5HFJK.js +17 -0
  28. package/dist/{SubscriptionPage-IDVEFLZ5.js.map → SubscriptionPage-5GE5HFJK.js.map} +1 -1
  29. package/dist/TeamTab-6RAULFLW.js +502 -0
  30. package/dist/TeamTab-6RAULFLW.js.map +1 -0
  31. package/dist/TrialWidget-Z6CLUCU3.js +7 -0
  32. package/dist/TrialWidget-Z6CLUCU3.js.map +1 -0
  33. package/dist/app/AdminShell.d.ts.map +1 -1
  34. package/dist/app/admin-app.d.ts.map +1 -1
  35. package/dist/app/config.d.ts +14 -0
  36. package/dist/app/config.d.ts.map +1 -1
  37. package/dist/app/routing.d.ts +6 -0
  38. package/dist/app/routing.d.ts.map +1 -1
  39. package/dist/app/scaffold.d.ts.map +1 -1
  40. package/dist/billing/index.js +9 -4
  41. package/dist/chunk-2H46WDG5.js +53 -0
  42. package/dist/chunk-2H46WDG5.js.map +1 -0
  43. package/dist/chunk-3HLX7YB2.js +94 -0
  44. package/dist/chunk-3HLX7YB2.js.map +1 -0
  45. package/dist/chunk-3OUD4XBK.js +3 -0
  46. package/dist/chunk-3OUD4XBK.js.map +1 -0
  47. package/dist/{chunk-IXSY4GPK.js → chunk-5D5QLMOQ.js} +5 -3
  48. package/dist/chunk-5D5QLMOQ.js.map +1 -0
  49. package/dist/chunk-5WR62X2R.js +1081 -0
  50. package/dist/chunk-5WR62X2R.js.map +1 -0
  51. package/dist/{chunk-KPYRTOCK.js → chunk-6CQFGMEO.js} +4 -140
  52. package/dist/chunk-6CQFGMEO.js.map +1 -0
  53. package/dist/chunk-6MMSYC2I.js +116 -0
  54. package/dist/chunk-6MMSYC2I.js.map +1 -0
  55. package/dist/chunk-6QFOE3UD.js +1466 -0
  56. package/dist/chunk-6QFOE3UD.js.map +1 -0
  57. package/dist/chunk-6UUFVQ2E.js +283 -0
  58. package/dist/chunk-6UUFVQ2E.js.map +1 -0
  59. package/dist/chunk-7WNLL3F6.js +7889 -0
  60. package/dist/chunk-7WNLL3F6.js.map +1 -0
  61. package/dist/chunk-A74IHAQE.js +439 -0
  62. package/dist/chunk-A74IHAQE.js.map +1 -0
  63. package/dist/{chunk-JOBRLBIQ.js → chunk-AFA6IO23.js} +4 -3
  64. package/dist/chunk-AFA6IO23.js.map +1 -0
  65. package/dist/{chunk-P5UIGQFY.js → chunk-ALCSIN3Z.js} +5 -5
  66. package/dist/chunk-ALCSIN3Z.js.map +1 -0
  67. package/dist/chunk-AWM4AMBS.js +39 -0
  68. package/dist/chunk-AWM4AMBS.js.map +1 -0
  69. package/dist/{chunk-OQARLKZN.js → chunk-BG5Z6KLN.js} +6 -18
  70. package/dist/chunk-BG5Z6KLN.js.map +1 -0
  71. package/dist/chunk-DPNFZONC.js +19 -0
  72. package/dist/chunk-DPNFZONC.js.map +1 -0
  73. package/dist/chunk-DRCV643L.js +193 -0
  74. package/dist/chunk-DRCV643L.js.map +1 -0
  75. package/dist/chunk-E6DF3VR6.js +206 -0
  76. package/dist/chunk-E6DF3VR6.js.map +1 -0
  77. package/dist/chunk-ENHCVGXZ.js +120 -0
  78. package/dist/chunk-ENHCVGXZ.js.map +1 -0
  79. package/dist/chunk-FK6RHGI7.js +56 -0
  80. package/dist/chunk-FK6RHGI7.js.map +1 -0
  81. package/dist/chunk-G5HNA5QK.js +233 -0
  82. package/dist/chunk-G5HNA5QK.js.map +1 -0
  83. package/dist/chunk-HCFO4Z6A.js +53 -0
  84. package/dist/chunk-HCFO4Z6A.js.map +1 -0
  85. package/dist/chunk-JFIU6NQP.js +51 -0
  86. package/dist/chunk-JFIU6NQP.js.map +1 -0
  87. package/dist/chunk-JRZJBMOY.js +3 -0
  88. package/dist/chunk-JRZJBMOY.js.map +1 -0
  89. package/dist/{chunk-3YP7KW47.js → chunk-JVVOL4BB.js} +41 -512
  90. package/dist/chunk-JVVOL4BB.js.map +1 -0
  91. package/dist/chunk-KAQ3UIAA.js +415 -0
  92. package/dist/chunk-KAQ3UIAA.js.map +1 -0
  93. package/dist/{chunk-P6XUDT2P.js → chunk-KJELMULA.js} +4 -51
  94. package/dist/chunk-KJELMULA.js.map +1 -0
  95. package/dist/chunk-KLO7DKXX.js +1651 -0
  96. package/dist/chunk-KLO7DKXX.js.map +1 -0
  97. package/dist/chunk-M2CMDWUI.js +75 -0
  98. package/dist/chunk-M2CMDWUI.js.map +1 -0
  99. package/dist/chunk-PQ47MLTH.js +255 -0
  100. package/dist/chunk-PQ47MLTH.js.map +1 -0
  101. package/dist/{chunk-VX3WJLKK.js → chunk-QD7BMUDF.js} +17 -8
  102. package/dist/chunk-QD7BMUDF.js.map +1 -0
  103. package/dist/{chunk-E4MESMNR.js → chunk-QTSKCPSW.js} +7 -10
  104. package/dist/chunk-QTSKCPSW.js.map +1 -0
  105. package/dist/chunk-S56FQKPL.js +186 -0
  106. package/dist/chunk-S56FQKPL.js.map +1 -0
  107. package/dist/chunk-SGAFPNMA.js +75 -0
  108. package/dist/chunk-SGAFPNMA.js.map +1 -0
  109. package/dist/{chunk-OD7P4L3D.js → chunk-SOKCLVM4.js} +3 -3
  110. package/dist/{chunk-OD7P4L3D.js.map → chunk-SOKCLVM4.js.map} +1 -1
  111. package/dist/chunk-TL42NKTH.js +3 -0
  112. package/dist/chunk-TL42NKTH.js.map +1 -0
  113. package/dist/chunk-U6ZCO2PS.js +2964 -0
  114. package/dist/chunk-U6ZCO2PS.js.map +1 -0
  115. package/dist/chunk-VSXNROYH.js +16 -0
  116. package/dist/chunk-VSXNROYH.js.map +1 -0
  117. package/dist/chunk-WPFZCYZL.js +619 -0
  118. package/dist/chunk-WPFZCYZL.js.map +1 -0
  119. package/dist/chunk-XFTNIBDW.js +23 -0
  120. package/dist/chunk-XFTNIBDW.js.map +1 -0
  121. package/dist/chunk-Y66JDL6I.js +1020 -0
  122. package/dist/chunk-Y66JDL6I.js.map +1 -0
  123. package/dist/chunk-YEHIBPIX.js +137 -0
  124. package/dist/chunk-YEHIBPIX.js.map +1 -0
  125. package/dist/chunk-YKPPCV2Z.js +53 -0
  126. package/dist/chunk-YKPPCV2Z.js.map +1 -0
  127. package/dist/components/chat/markdown.d.ts.map +1 -1
  128. package/dist/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
  129. package/dist/components/settings/SettingsPage.d.ts.map +1 -1
  130. package/dist/connect-XKQJOWR7.js +18 -0
  131. package/dist/connect-XKQJOWR7.js.map +1 -0
  132. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  133. package/dist/crud/CrudListView.d.ts.map +1 -1
  134. package/dist/crud/CrudPage.d.ts.map +1 -1
  135. package/dist/crud/RelatedListTab.d.ts.map +1 -1
  136. package/dist/crud/archetypes/EntityLink.d.ts +1 -1
  137. package/dist/crud/archetypes/EntityLink.d.ts.map +1 -1
  138. package/dist/crud/record-chrome.d.ts.map +1 -1
  139. package/dist/entity-routes-PGU7EHAD.js +4 -0
  140. package/dist/{entity-routes-LXOBW5IB.js.map → entity-routes-PGU7EHAD.js.map} +1 -1
  141. package/dist/hooks/useModuleNavigation.d.ts.map +1 -1
  142. package/dist/impersonation/ImpersonateDialog.d.ts.map +1 -1
  143. package/dist/impersonation/session.d.ts.map +1 -1
  144. package/dist/index.d.ts +1 -1
  145. package/dist/index.d.ts.map +1 -1
  146. package/dist/index.js +11902 -33551
  147. package/dist/index.js.map +1 -1
  148. package/dist/lib/entity-routes.d.ts.map +1 -1
  149. package/dist/org/index.js +4 -2
  150. package/dist/plugins/PluginRegistryManager.d.ts.map +1 -1
  151. package/dist/settings/MessageTemplateRow.d.ts.map +1 -1
  152. package/dist/timeline/PersonTimeline.d.ts.map +1 -1
  153. package/dist/timeline/TimelineList.d.ts.map +1 -1
  154. package/dist/units/UnitRailPage.d.ts.map +1 -1
  155. package/dist/units/useUnitFilter.d.ts.map +1 -1
  156. package/package.json +5 -5
  157. package/dist/PersonTimeline-X7JTPUWJ.js +0 -3
  158. package/dist/ScheduleEditor-L2UJXKMY.js +0 -6
  159. package/dist/SubscriptionPage-IDVEFLZ5.js +0 -8
  160. package/dist/chunk-3YP7KW47.js.map +0 -1
  161. package/dist/chunk-E4MESMNR.js.map +0 -1
  162. package/dist/chunk-IXSY4GPK.js.map +0 -1
  163. package/dist/chunk-JOBRLBIQ.js.map +0 -1
  164. package/dist/chunk-KPYRTOCK.js.map +0 -1
  165. package/dist/chunk-OQARLKZN.js.map +0 -1
  166. package/dist/chunk-P5UIGQFY.js.map +0 -1
  167. package/dist/chunk-P6XUDT2P.js.map +0 -1
  168. package/dist/chunk-VX3WJLKK.js.map +0 -1
  169. package/dist/entity-routes-LXOBW5IB.js +0 -4
@@ -0,0 +1,120 @@
1
+ import { Hint, Switch, cn, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input, Button, InfoHint } from '@fayz-ai/ui';
2
+ import { tl } from '@fayz-ai/core';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ // src/plugins/SettingsGroup.tsx
6
+ var pendingBadgesVisible = true;
7
+ function setPendingSettingBadges(visible) {
8
+ pendingBadgesVisible = visible;
9
+ }
10
+ function PendingSettingBadge() {
11
+ if (!pendingBadgesVisible) return null;
12
+ const text = tl("Saved, but the system does not use this option yet.", "Salva, mas o sistema ainda n\xE3o usa esta op\xE7\xE3o.");
13
+ return /* @__PURE__ */ jsx(Hint, { label: text, side: "top", children: /* @__PURE__ */ jsx(
14
+ "span",
15
+ {
16
+ tabIndex: 0,
17
+ "aria-label": text,
18
+ className: "ml-2 inline-flex shrink-0 items-center rounded-full border border-dashed border-muted-foreground/40 px-1.5 py-px align-middle text-[10px] font-medium text-muted-foreground",
19
+ children: tl("Coming soon", "Em breve")
20
+ }
21
+ ) });
22
+ }
23
+ function RowLabel({ label, pending }) {
24
+ return /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
25
+ label,
26
+ pending && /* @__PURE__ */ jsx(PendingSettingBadge, {})
27
+ ] });
28
+ }
29
+ function ToggleRow({ label, description, checked, onChange, disabled, pending }) {
30
+ return /* @__PURE__ */ jsxs("label", { className: cn(
31
+ "flex items-center justify-between gap-4 py-3 cursor-pointer",
32
+ disabled && "opacity-50 cursor-not-allowed"
33
+ ), children: [
34
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
35
+ /* @__PURE__ */ jsx(RowLabel, { label, pending }),
36
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: description })
37
+ ] }),
38
+ /* @__PURE__ */ jsx(Switch, { size: "md", checked, onCheckedChange: onChange, disabled })
39
+ ] });
40
+ }
41
+ function SelectRow({ label, description, value, options, onChange, disabled, pending }) {
42
+ return /* @__PURE__ */ jsxs("label", { className: cn(
43
+ "flex items-center justify-between gap-4 py-3",
44
+ disabled && "opacity-50 cursor-not-allowed"
45
+ ), children: [
46
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
47
+ /* @__PURE__ */ jsx(RowLabel, { label, pending }),
48
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: description })
49
+ ] }),
50
+ /* @__PURE__ */ jsxs(Select, { value, onValueChange: onChange, disabled, children: [
51
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-auto gap-2 bg-card px-2", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
52
+ /* @__PURE__ */ jsx(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
53
+ ] })
54
+ ] });
55
+ }
56
+ function TextRow({ label, description, value, onChange, onBlurCommit, placeholder, hint, invalid, disabled, pending }) {
57
+ return /* @__PURE__ */ jsxs("label", { className: cn("block py-3", disabled && "opacity-50"), children: [
58
+ /* @__PURE__ */ jsx(RowLabel, { label, pending }),
59
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: description }),
60
+ /* @__PURE__ */ jsx(
61
+ Input,
62
+ {
63
+ type: "text",
64
+ value,
65
+ disabled,
66
+ placeholder,
67
+ "aria-invalid": invalid || void 0,
68
+ onChange: (e) => onChange(e.target.value),
69
+ onBlur: onBlurCommit,
70
+ onKeyDown: (e) => {
71
+ if (e.key === "Enter") onBlurCommit?.();
72
+ },
73
+ className: cn("mt-2 max-w-xs", invalid && "border-destructive")
74
+ }
75
+ ),
76
+ hint && /* @__PURE__ */ jsx("p", { className: cn("mt-1 text-xs", invalid ? "text-destructive" : "text-muted-foreground"), children: hint })
77
+ ] });
78
+ }
79
+ function MultiSelectRow({ label, description, value, options, onChange, disabled, pending }) {
80
+ const chosen = new Set(value);
81
+ return /* @__PURE__ */ jsxs("div", { className: cn("py-3", disabled && "opacity-50"), children: [
82
+ /* @__PURE__ */ jsx(RowLabel, { label, pending }),
83
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: description }),
84
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-1.5", children: options.map((opt) => {
85
+ const on = chosen.has(opt.value);
86
+ return /* @__PURE__ */ jsx(
87
+ Button,
88
+ {
89
+ variant: "bare",
90
+ size: "bare",
91
+ type: "button",
92
+ role: "checkbox",
93
+ "aria-checked": on,
94
+ disabled,
95
+ onClick: () => onChange(on ? value.filter((v) => v !== opt.value) : [...value, opt.value]),
96
+ className: cn(
97
+ "rounded-full border px-3 py-1 text-xs font-medium transition-colors",
98
+ on ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:bg-muted/60",
99
+ disabled && "cursor-not-allowed"
100
+ ),
101
+ children: opt.label
102
+ },
103
+ opt.value
104
+ );
105
+ }) })
106
+ ] });
107
+ }
108
+ function SettingsGroup({ title, description, children }) {
109
+ return /* @__PURE__ */ jsxs("div", { children: [
110
+ title && /* @__PURE__ */ jsx("div", { className: "px-1 pb-1 pt-4 first:pt-0", children: /* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-1.5 text-sm font-semibold", children: [
111
+ title,
112
+ description && /* @__PURE__ */ jsx(InfoHint, { text: description })
113
+ ] }) }),
114
+ /* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card shadow-sm px-5 divide-y", children })
115
+ ] });
116
+ }
117
+
118
+ export { MultiSelectRow, PendingSettingBadge, SelectRow, SettingsGroup, TextRow, ToggleRow, setPendingSettingBadges };
119
+ //# sourceMappingURL=chunk-ENHCVGXZ.js.map
120
+ //# sourceMappingURL=chunk-ENHCVGXZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/SettingsGroup.tsx"],"names":[],"mappings":"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","file":"chunk-ENHCVGXZ.js","sourcesContent":["import React from 'react'\nimport { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, cn, Hint, InfoHint } from '@fayz-ai/ui'\nimport { tl } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Pending — the row saves, but no feature reads it yet\n// ---------------------------------------------------------------------------\n// An honest map for whoever runs the account (and for us): a switch that does\n// nothing yet says so instead of passing for a working one. Shown to everyone\n// for now; one call hides it (an app, or later a platform-admin check).\n\nlet pendingBadgesVisible = true\n\n/** Show or hide the \"Em breve\" mark on settings rows that nothing reads yet. */\nexport function setPendingSettingBadges(visible: boolean): void {\n pendingBadgesVisible = visible\n}\n\nexport function PendingSettingBadge() {\n if (!pendingBadgesVisible) return null\n const text = tl('Saved, but the system does not use this option yet.', 'Salva, mas o sistema ainda não usa esta opção.')\n return (\n <Hint label={text} side=\"top\">\n <span\n tabIndex={0}\n aria-label={text}\n className=\"ml-2 inline-flex shrink-0 items-center rounded-full border border-dashed border-muted-foreground/40 px-1.5 py-px align-middle text-[10px] font-medium text-muted-foreground\"\n >\n {tl('Coming soon', 'Em breve')}\n </span>\n </Hint>\n )\n}\n\nfunction RowLabel({ label, pending }: { label: string; pending?: boolean }) {\n return (\n <p className=\"text-sm font-medium\">\n {label}\n {pending && <PendingSettingBadge />}\n </p>\n )\n}\n\n// ---------------------------------------------------------------------------\n// ToggleRow — a single setting with label, description, and checkbox\n// ---------------------------------------------------------------------------\n\nexport function ToggleRow({ label, description, checked, onChange, disabled, pending }: {\n /** Saves, but no feature reads it yet — shows the \"Em breve\" mark. */\n pending?: boolean\n label: string\n description?: string\n checked: boolean\n onChange: (checked: boolean) => void\n disabled?: boolean\n}) {\n return (\n <label className={cn(\n 'flex items-center justify-between gap-4 py-3 cursor-pointer',\n disabled && 'opacity-50 cursor-not-allowed',\n )}>\n <div className=\"min-w-0\">\n <RowLabel label={label} pending={pending} />\n {description && <p className=\"text-xs text-muted-foreground mt-0.5\">{description}</p>}\n </div>\n <Switch size=\"md\" checked={checked} onCheckedChange={onChange} disabled={disabled} />\n </label>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SelectRow — a single setting with label, description, and select dropdown\n// ---------------------------------------------------------------------------\n\nexport function SelectRow({ label, description, value, options, onChange, disabled, pending }: {\n pending?: boolean\n label: string\n description?: string\n value: string\n options: { value: string; label: string }[]\n onChange: (value: string) => void\n disabled?: boolean\n}) {\n return (\n <label className={cn(\n 'flex items-center justify-between gap-4 py-3',\n disabled && 'opacity-50 cursor-not-allowed',\n )}>\n <div className=\"min-w-0\">\n <RowLabel label={label} pending={pending} />\n {description && <p className=\"text-xs text-muted-foreground mt-0.5\">{description}</p>}\n </div>\n <Select value={value} onValueChange={onChange} disabled={disabled}>\n <SelectTrigger className=\"h-8 w-auto gap-2 bg-card px-2\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n )\n}\n\n// ---------------------------------------------------------------------------\n// TextRow — a single setting with label, description, and a text input\n// ---------------------------------------------------------------------------\n// Stacked rather than side-by-side, because this is the row that can be WRONG:\n// an input with a shape (\"8 dígitos\") needs room under it for the sentence that\n// says so, and a hint squeezed into a flex row beside a 40%-wide input is a\n// hint nobody reads.\n\nexport function TextRow({ label, description, value, onChange, onBlurCommit, placeholder, hint, invalid, disabled, pending }: {\n pending?: boolean\n label: string\n description?: string\n value: string\n onChange: (value: string) => void\n /**\n * Called on blur and on Enter — where a value that has to be PARSED should be\n * committed. Auto-saving a numeric setting per keystroke writes every prefix\n * on the way to the number ('1' and '19' before '19,90'), and each of those is\n * a real freight the store briefly charged.\n */\n onBlurCommit?: () => void\n placeholder?: string\n /** Shown under the input; turns into the refusal when `invalid`. */\n hint?: string\n invalid?: boolean\n disabled?: boolean\n}) {\n return (\n <label className={cn('block py-3', disabled && 'opacity-50')}>\n <RowLabel label={label} pending={pending} />\n {description && <p className=\"text-xs text-muted-foreground mt-0.5\">{description}</p>}\n <Input\n type=\"text\"\n value={value}\n disabled={disabled}\n placeholder={placeholder}\n aria-invalid={invalid || undefined}\n onChange={(e) => onChange(e.target.value)}\n onBlur={onBlurCommit}\n onKeyDown={(e) => { if (e.key === 'Enter') onBlurCommit?.() }}\n className={cn('mt-2 max-w-xs', invalid && 'border-destructive')}\n />\n {hint && (\n <p className={cn('mt-1 text-xs', invalid ? 'text-destructive' : 'text-muted-foreground')}>{hint}</p>\n )}\n </label>\n )\n}\n\n// ---------------------------------------------------------------------------\n// MultiSelectRow — a setting whose value is a LIST of known options\n// ---------------------------------------------------------------------------\n// Chips and not a <select multiple>: the values here are things a shopkeeper\n// recognises (\"Pix\", \"Boleto\") and the whole point of the control is that every\n// one of them is visible and none can be misspelled. A native multiple-select\n// hides the unchosen ones behind a scroll and needs a modifier key to use.\n\nexport function MultiSelectRow({ label, description, value, options, onChange, disabled, pending }: {\n pending?: boolean\n label: string\n description?: string\n value: string[]\n options: { value: string; label: string }[]\n onChange: (value: string[]) => void\n disabled?: boolean\n}) {\n const chosen = new Set(value)\n return (\n <div className={cn('py-3', disabled && 'opacity-50')}>\n <RowLabel label={label} pending={pending} />\n {description && <p className=\"text-xs text-muted-foreground mt-0.5\">{description}</p>}\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {options.map((opt) => {\n const on = chosen.has(opt.value)\n return (\n <Button\n variant=\"bare\"\n size=\"bare\"\n key={opt.value}\n type=\"button\"\n role=\"checkbox\"\n aria-checked={on}\n disabled={disabled}\n onClick={() =>\n onChange(on ? value.filter((v) => v !== opt.value) : [...value, opt.value])\n }\n className={cn(\n 'rounded-full border px-3 py-1 text-xs font-medium transition-colors',\n on\n ? 'border-primary bg-primary/10 text-primary'\n : 'border-border text-muted-foreground hover:bg-muted/60',\n disabled && 'cursor-not-allowed',\n )}\n >\n {opt.label}\n </Button>\n )\n })}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SettingsGroup — a bordered section with title and children\n// ---------------------------------------------------------------------------\n\nexport function SettingsGroup({ title, description, children }: {\n /** Omitted when the surface around it already names the group — no empty heading block. */\n title?: string\n description?: string\n children: React.ReactNode\n}) {\n return (\n <div>\n {title && (\n <div className=\"px-1 pb-1 pt-4 first:pt-0\">\n <h3 className=\"flex items-center gap-1.5 text-sm font-semibold\">\n {title}\n {description && <InfoHint text={description} />}\n </h3>\n </div>\n )}\n <div className=\"rounded-card border bg-card shadow-sm px-5 divide-y\">\n {children}\n </div>\n </div>\n )\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import { getSupabaseClientOptional } from '@fayz-ai/core';
2
+
3
+ // src/lib/field-rules-db.ts
4
+ function fieldRuleReference(entityKey) {
5
+ return entityKey.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
6
+ }
7
+ function stateToOverride(state) {
8
+ if (state === "required") return { required: true, showInForm: true };
9
+ if (state === "hidden") return { showInForm: false, showInTable: false, showInDetail: false };
10
+ return { showInForm: true };
11
+ }
12
+ function rulesToOverrides(db) {
13
+ if (!db.length) return void 0;
14
+ const out = {};
15
+ for (const row of db) out[row.field_key] = { ...out[row.field_key], ...stateToOverride(row.state) };
16
+ return out;
17
+ }
18
+ function client() {
19
+ return getSupabaseClientOptional();
20
+ }
21
+ async function resolveFieldRules(entityKey, context, unitId) {
22
+ const c = client();
23
+ if (!c) return [];
24
+ const { data, error } = await c.rpc("field_rules_resolve", {
25
+ p_context: context,
26
+ p_reference: fieldRuleReference(entityKey),
27
+ p_unit: unitId ?? null
28
+ });
29
+ if (error) throw error;
30
+ return data ?? [];
31
+ }
32
+ async function listFieldRules(tenantId, unitId = null) {
33
+ const c = client();
34
+ if (!c) return [];
35
+ const q = c.from("field_rules").select("id, context, reference, field_key, state, role_key").eq("tenant_id", tenantId);
36
+ const { data, error } = await (unitId ? q.eq("unit_id", unitId) : q.is("unit_id", null));
37
+ if (error) throw error;
38
+ return data ?? [];
39
+ }
40
+ async function setFieldRule(tenantId, rule, state, unitId = null) {
41
+ const c = client();
42
+ if (!c) throw new Error("No backend mounted");
43
+ let q = c.from("field_rules").select("id").eq("tenant_id", tenantId);
44
+ q = unitId ? q.eq("unit_id", unitId) : q.is("unit_id", null);
45
+ q = q.eq("context", rule.context).eq("reference", rule.reference).eq("field_key", rule.field_key);
46
+ q = rule.role_key ? q.eq("role_key", rule.role_key) : q.is("role_key", null);
47
+ const found = await q.maybeSingle();
48
+ if (found.error) throw found.error;
49
+ const id = found.data?.id;
50
+ const res = state === null ? id ? await c.from("field_rules").delete().eq("id", id) : { error: null } : id ? await c.from("field_rules").update({ state }).eq("id", id) : await c.from("field_rules").insert({ tenant_id: tenantId, unit_id: unitId, ...rule, state });
51
+ if (res.error) throw res.error;
52
+ }
53
+
54
+ export { fieldRuleReference, listFieldRules, resolveFieldRules, rulesToOverrides, setFieldRule };
55
+ //# sourceMappingURL=chunk-FK6RHGI7.js.map
56
+ //# sourceMappingURL=chunk-FK6RHGI7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/field-rules-db.ts"],"names":[],"mappings":";;;AASO,SAAS,mBAAmB,SAAA,EAA2B;AAC5D,EAAA,OAAO,SAAA,CAAU,aAAY,CAAE,OAAA,CAAQ,eAAe,GAAG,CAAA,CAAE,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA;AACnF;AAEO,SAAS,gBAAgB,KAAA,EAA0C;AACxE,EAAA,IAAI,UAAU,UAAA,EAAY,OAAO,EAAE,QAAA,EAAU,IAAA,EAAM,YAAY,IAAA,EAAK;AACpE,EAAA,IAAI,KAAA,KAAU,UAAU,OAAO,EAAE,YAAY,KAAA,EAAO,WAAA,EAAa,KAAA,EAAO,YAAA,EAAc,KAAA,EAAM;AAC5F,EAAA,OAAO,EAAE,YAAY,IAAA,EAAK;AAC5B;AAEO,SAAS,iBAAiB,EAAA,EAAuF;AACtH,EAAA,IAAI,CAAC,EAAA,CAAG,MAAA,EAAQ,OAAO,MAAA;AACvB,EAAA,MAAM,MAAwB,EAAC;AAC/B,EAAA,KAAA,MAAW,OAAO,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,SAAS,IAAI,EAAE,GAAG,GAAA,CAAI,GAAA,CAAI,SAAS,CAAA,EAAG,GAAG,eAAA,CAAgB,GAAA,CAAI,KAAK,CAAA,EAAE;AAClG,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,MAAA,GAAc;AACrB,EAAA,OAAO,yBAAA,EAA0B;AACnC;AAGA,eAAsB,iBAAA,CAAkB,SAAA,EAAmB,OAAA,EAA2B,MAAA,EAAwB;AAC5G,EAAA,MAAM,IAAI,MAAA,EAAO;AACjB,EAAA,IAAI,CAAC,CAAA,EAAG,OAAO,EAAC;AAChB,EAAA,MAAM,EAAE,IAAA,EAAM,KAAA,KAAU,MAAM,CAAA,CAAE,IAAI,qBAAA,EAAuB;AAAA,IACzD,SAAA,EAAW,OAAA;AAAA,IAAS,WAAA,EAAa,mBAAmB,SAAS,CAAA;AAAA,IAAG,QAAQ,MAAA,IAAU;AAAA,GACnF,CAAA;AACD,EAAA,IAAI,OAAO,MAAM,KAAA;AACjB,EAAA,OAAQ,QAAQ,EAAC;AACnB;AAaA,eAAsB,cAAA,CAAe,QAAA,EAAkB,MAAA,GAAwB,IAAA,EAA+B;AAC5G,EAAA,MAAM,IAAI,MAAA,EAAO;AACjB,EAAA,IAAI,CAAC,CAAA,EAAG,OAAO,EAAC;AAChB,EAAA,MAAM,CAAA,GAAI,CAAA,CAAE,IAAA,CAAK,aAAa,CAAA,CAC3B,OAAO,oDAAoD,CAAA,CAC3D,EAAA,CAAG,WAAA,EAAa,QAAQ,CAAA;AAC3B,EAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,OAAO,MAAA,GAAS,CAAA,CAAE,EAAA,CAAG,SAAA,EAAW,MAAM,CAAA,GAAI,CAAA,CAAE,EAAA,CAAG,WAAW,IAAI,CAAA,CAAA;AACtF,EAAA,IAAI,OAAO,MAAM,KAAA;AACjB,EAAA,OAAQ,QAAQ,EAAC;AACnB;AAGA,eAAsB,YAAA,CACpB,QAAA,EACA,IAAA,EACA,KAAA,EACA,SAAwB,IAAA,EACT;AACf,EAAA,MAAM,IAAI,MAAA,EAAO;AACjB,EAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,MAAM,oBAAoB,CAAA;AAE5C,EAAA,IAAI,CAAA,GAAI,CAAA,CAAE,IAAA,CAAK,aAAa,CAAA,CAAE,OAAO,IAAI,CAAA,CAAE,EAAA,CAAG,WAAA,EAAa,QAAQ,CAAA;AACnE,EAAA,CAAA,GAAI,MAAA,GAAS,EAAE,EAAA,CAAG,SAAA,EAAW,MAAM,CAAA,GAAI,CAAA,CAAE,EAAA,CAAG,SAAA,EAAW,IAAI,CAAA;AAC3D,EAAA,CAAA,GAAI,CAAA,CACD,EAAA,CAAG,SAAA,EAAW,IAAA,CAAK,OAAO,CAAA,CAAE,EAAA,CAAG,WAAA,EAAa,IAAA,CAAK,SAAS,CAAA,CAAE,EAAA,CAAG,WAAA,EAAa,KAAK,SAAS,CAAA;AAC7F,EAAA,CAAA,GAAI,IAAA,CAAK,QAAA,GAAW,CAAA,CAAE,EAAA,CAAG,UAAA,EAAY,IAAA,CAAK,QAAQ,CAAA,GAAI,CAAA,CAAE,EAAA,CAAG,UAAA,EAAY,IAAI,CAAA;AAC3E,EAAA,MAAM,KAAA,GAAQ,MAAM,CAAA,CAAE,WAAA,EAAY;AAClC,EAAA,IAAI,KAAA,CAAM,KAAA,EAAO,MAAM,KAAA,CAAM,KAAA;AAC7B,EAAA,MAAM,EAAA,GAAM,MAAM,IAAA,EAAgC,EAAA;AAClD,EAAA,MAAM,GAAA,GAAM,UAAU,IAAA,GACjB,EAAA,GAAK,MAAM,CAAA,CAAE,IAAA,CAAK,aAAa,CAAA,CAAE,MAAA,EAAO,CAAE,GAAG,IAAA,EAAM,EAAE,IAAI,EAAE,KAAA,EAAO,MAAK,GACxE,EAAA,GACE,MAAM,CAAA,CAAE,IAAA,CAAK,aAAa,EAAE,MAAA,CAAO,EAAE,OAAO,CAAA,CAAE,GAAG,IAAA,EAAM,EAAE,CAAA,GACzD,MAAM,CAAA,CAAE,IAAA,CAAK,aAAa,CAAA,CAAE,MAAA,CAAO,EAAE,SAAA,EAAW,QAAA,EAAU,SAAS,MAAA,EAAQ,GAAG,IAAA,EAAM,KAAA,EAAO,CAAA;AACjG,EAAA,IAAI,GAAA,CAAI,KAAA,EAAO,MAAM,GAAA,CAAI,KAAA;AAC3B","file":"chunk-FK6RHGI7.js","sourcesContent":["import { getSupabaseClientOptional } from '@fayz-ai/core'\nimport type { EntityFieldRules, FieldRuleOverride } from '../types/crud'\n\nexport type FieldRuleState = 'hidden' | 'visible' | 'required'\nexport type FieldRuleContext = 'main_form' | 'quick_search' | 'person'\n\n/** A rule belongs to the CRUD entity (archetype:kind, e.g. `person:customer`),\n * stored as `person_customer` because the column only takes snake case. Field\n * keys are the entity's own FieldDef keys. */\nexport function fieldRuleReference(entityKey: string): string {\n return entityKey.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '')\n}\n\nexport function stateToOverride(state: FieldRuleState): FieldRuleOverride {\n if (state === 'required') return { required: true, showInForm: true }\n if (state === 'hidden') return { showInForm: false, showInTable: false, showInDetail: false }\n return { showInForm: true }\n}\n\nexport function rulesToOverrides(db: Array<{ field_key: string; state: FieldRuleState }>): EntityFieldRules | undefined {\n if (!db.length) return undefined\n const out: EntityFieldRules = {}\n for (const row of db) out[row.field_key] = { ...out[row.field_key], ...stateToOverride(row.state) }\n return out\n}\n\nfunction client(): any {\n return getSupabaseClientOptional()\n}\n\n/** Effective rules for the signed-in person (role and unit applied server-side). */\nexport async function resolveFieldRules(entityKey: string, context: FieldRuleContext, unitId?: string | null) {\n const c = client()\n if (!c) return []\n const { data, error } = await c.rpc('field_rules_resolve', {\n p_context: context, p_reference: fieldRuleReference(entityKey), p_unit: unitId ?? null,\n })\n if (error) throw error\n return (data ?? []) as Array<{ field_key: string; state: FieldRuleState }>\n}\n\nexport interface FieldRuleRow {\n id: string\n context: FieldRuleContext\n reference: string\n field_key: string\n state: FieldRuleState\n role_key: string | null\n}\n\n/** Rules as written for one scope — the whole account (`unitId` null) or one\n * unit's own — for the editor. */\nexport async function listFieldRules(tenantId: string, unitId: string | null = null): Promise<FieldRuleRow[]> {\n const c = client()\n if (!c) return []\n const q = c.from('field_rules')\n .select('id, context, reference, field_key, state, role_key')\n .eq('tenant_id', tenantId)\n const { data, error } = await (unitId ? q.eq('unit_id', unitId) : q.is('unit_id', null))\n if (error) throw error\n return (data ?? []) as FieldRuleRow[]\n}\n\n/** Sets one rule, or removes it with `state = null` (back to the screen's default). */\nexport async function setFieldRule(\n tenantId: string,\n rule: { context: FieldRuleContext; reference: string; field_key: string; role_key: string | null },\n state: FieldRuleState | null,\n unitId: string | null = null,\n): Promise<void> {\n const c = client()\n if (!c) throw new Error('No backend mounted')\n // The unique index is on COALESCE expressions, which PostgREST upsert cannot name.\n let q = c.from('field_rules').select('id').eq('tenant_id', tenantId)\n q = unitId ? q.eq('unit_id', unitId) : q.is('unit_id', null)\n q = q\n .eq('context', rule.context).eq('reference', rule.reference).eq('field_key', rule.field_key)\n q = rule.role_key ? q.eq('role_key', rule.role_key) : q.is('role_key', null)\n const found = await q.maybeSingle()\n if (found.error) throw found.error\n const id = (found.data as { id: string } | null)?.id\n const res = state === null\n ? (id ? await c.from('field_rules').delete().eq('id', id) : { error: null })\n : id\n ? await c.from('field_rules').update({ state }).eq('id', id)\n : await c.from('field_rules').insert({ tenant_id: tenantId, unit_id: unitId, ...rule, state })\n if (res.error) throw res.error\n}\n"]}
@@ -0,0 +1,233 @@
1
+ import { useAppConfigStore, activePluginIds, hasPlugin } from './chunk-5D5QLMOQ.js';
2
+ import { getSupabaseClientOptional } from './chunk-KJELMULA.js';
3
+ import { useOrganizationStore } from './chunk-YKPPCV2Z.js';
4
+ import { invalidateAppConfig, errorMessage, toTenantPluginBindings, fetchAppConfig } from '@fayz-ai/core';
5
+ import { useCallback, useLayoutEffect, useEffect, useMemo } from 'react';
6
+
7
+ // src/app/app-config-cache.ts
8
+ var STORAGE_KEY = "fayz:app-config";
9
+ var VERSION = 1;
10
+ var APP_CONFIG_TTL_MS = 5 * 60 * 1e3;
11
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
12
+ var MAX_BYTES = 256 * 1024;
13
+ function storage() {
14
+ try {
15
+ return globalThis.localStorage ?? null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ var keyFor = (tenantId) => `${STORAGE_KEY}:${tenantId}`;
21
+ function readAppConfigSnapshot(tenantId) {
22
+ const store = storage();
23
+ if (!store) return null;
24
+ try {
25
+ const raw = store.getItem(keyFor(tenantId));
26
+ if (!raw) return null;
27
+ const snap = JSON.parse(raw);
28
+ if (snap.version !== VERSION || snap.tenantId !== tenantId) return null;
29
+ const age = Date.now() - snap.savedAt;
30
+ if (age > MAX_AGE_MS) {
31
+ store.removeItem(keyFor(tenantId));
32
+ return null;
33
+ }
34
+ return { config: snap.config, stale: age > APP_CONFIG_TTL_MS };
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ function writeAppConfigSnapshot(tenantId, config) {
40
+ const store = storage();
41
+ if (!store) return;
42
+ try {
43
+ const payload = JSON.stringify({ version: VERSION, tenantId, savedAt: Date.now(), config });
44
+ if (payload.length > MAX_BYTES) return;
45
+ store.setItem(keyFor(tenantId), payload);
46
+ } catch {
47
+ }
48
+ }
49
+ function clearAppConfigSnapshot(tenantId) {
50
+ const store = storage();
51
+ if (!store) return;
52
+ try {
53
+ if (tenantId) {
54
+ store.removeItem(keyFor(tenantId));
55
+ return;
56
+ }
57
+ for (let i = store.length - 1; i >= 0; i -= 1) {
58
+ const key = store.key(i);
59
+ if (key?.startsWith(`${STORAGE_KEY}:`)) store.removeItem(key);
60
+ }
61
+ } catch {
62
+ }
63
+ }
64
+ var CONFIG_POLL_MS = 6e4;
65
+ var EMPTY = { tenant_id: null, plugins: [], apps: [], nav: [], permissions: [] };
66
+ async function loadFor(tenantId, force = false) {
67
+ const supabase = getSupabaseClientOptional();
68
+ if (!supabase) return { ...EMPTY, tenant_id: tenantId };
69
+ return fetchAppConfig(supabase, { tenantId: tenantId ?? void 0, force });
70
+ }
71
+ function useAppConfig(options) {
72
+ const enabled = options?.enabled !== false;
73
+ const currentOrg = useOrganizationStore((state) => state.currentOrg);
74
+ const tenantId = currentOrg?.id ?? null;
75
+ const config = useAppConfigStore((state) => state.config);
76
+ const configTenantId = useAppConfigStore((state) => state.tenantId);
77
+ const beginTenant = useAppConfigStore((state) => state.beginTenant);
78
+ const initializing = useAppConfigStore((state) => state.initializing);
79
+ const stale = useAppConfigStore((state) => state.stale);
80
+ const error = useAppConfigStore((state) => state.error);
81
+ const setConfig = useAppConfigStore((state) => state.setConfig);
82
+ const setStale = useAppConfigStore((state) => state.setStale);
83
+ const setError = useAppConfigStore((state) => state.setError);
84
+ const reset = useAppConfigStore((state) => state.reset);
85
+ const refresh = useCallback(async () => {
86
+ setStale(true);
87
+ try {
88
+ if (tenantId) {
89
+ invalidateAppConfig(tenantId);
90
+ clearAppConfigSnapshot(tenantId);
91
+ }
92
+ const next = await loadFor(tenantId, true);
93
+ if (tenantId) writeAppConfigSnapshot(tenantId, next);
94
+ setConfig(next, tenantId);
95
+ } catch (err) {
96
+ setError(errorMessage(err));
97
+ } finally {
98
+ setStale(false);
99
+ }
100
+ }, [tenantId, setConfig, setError, setStale]);
101
+ useLayoutEffect(() => {
102
+ if (!enabled) return;
103
+ if (configTenantId !== tenantId) beginTenant(tenantId);
104
+ }, [enabled, tenantId, configTenantId, beginTenant]);
105
+ useLayoutEffect(() => {
106
+ if (!enabled || !tenantId) return;
107
+ const snap = readAppConfigSnapshot(tenantId);
108
+ if (!snap) return;
109
+ setConfig(snap.config, tenantId);
110
+ if (snap.stale) setStale(true);
111
+ }, [enabled, tenantId, setConfig, setStale]);
112
+ useEffect(() => {
113
+ if (!enabled) {
114
+ reset();
115
+ return;
116
+ }
117
+ let cancelled = false;
118
+ void (async () => {
119
+ try {
120
+ const next = await loadFor(tenantId);
121
+ if (cancelled) return;
122
+ if (tenantId) writeAppConfigSnapshot(tenantId, next);
123
+ setConfig(next, tenantId);
124
+ setStale(false);
125
+ } catch (err) {
126
+ if (!cancelled) setError(errorMessage(err));
127
+ }
128
+ })();
129
+ return () => {
130
+ cancelled = true;
131
+ };
132
+ }, [enabled, tenantId, setConfig, setError, setStale, reset]);
133
+ useEffect(() => {
134
+ if (!enabled || !tenantId) return;
135
+ const supabase = getSupabaseClientOptional();
136
+ if (!supabase) return;
137
+ let cancelled = false;
138
+ let seen = null;
139
+ const poll = async () => {
140
+ try {
141
+ const { data, error: error2 } = await supabase.rpc("tenant_config_version");
142
+ if (cancelled || error2 || !data) return;
143
+ const stamp = String(data);
144
+ if (seen === null) {
145
+ seen = stamp;
146
+ return;
147
+ }
148
+ if (stamp !== seen) {
149
+ seen = stamp;
150
+ invalidateAppConfig(tenantId);
151
+ const next = await loadFor(tenantId, true);
152
+ writeAppConfigSnapshot(tenantId, next);
153
+ setConfig(next, tenantId);
154
+ }
155
+ } catch {
156
+ }
157
+ };
158
+ const timer = setInterval(() => void poll(), CONFIG_POLL_MS);
159
+ void poll();
160
+ return () => {
161
+ cancelled = true;
162
+ clearInterval(timer);
163
+ };
164
+ }, [enabled, tenantId, setConfig]);
165
+ const activePlugins = useMemo(() => activePluginIds(config), [config]);
166
+ const bindings = useMemo(() => toTenantPluginBindings(config), [config]);
167
+ const has = useCallback(
168
+ (plugin, facet) => hasPlugin(config, plugin, facet),
169
+ [config]
170
+ );
171
+ const ready = !tenantId || configTenantId === tenantId && !initializing;
172
+ return { config, initializing, ready, stale, error, activePlugins, has, bindings, refresh };
173
+ }
174
+ var MODULE_FAMILIES = [
175
+ {
176
+ id: "sales",
177
+ label: ["Sales & floor", "Vendas e sal\xE3o"],
178
+ line: ["Selling at the counter, the table and online.", "Vender no balc\xE3o, na mesa e online."],
179
+ plugins: ["orders", "tables", "menu", "reservations", "shop", "payments", "sites", "marketplaces"]
180
+ },
181
+ {
182
+ id: "stock",
183
+ label: ["Stock & production", "Estoque e produ\xE7\xE3o"],
184
+ line: ["What comes in, what is made, what goes out.", "O que entra, o que se produz, o que sai."],
185
+ plugins: ["inventory", "production", "kitchen"]
186
+ },
187
+ {
188
+ id: "care",
189
+ label: ["Attendance & schedule", "Atendimento e agenda"],
190
+ line: ["Bookings, records and the service itself.", "Agendamento, prontu\xE1rio e o atendimento em si."],
191
+ plugins: ["agenda", "clinical", "forms", "custom_forms", "scribe", "courses", "tiss", "pet"]
192
+ },
193
+ {
194
+ id: "finance",
195
+ label: ["Finance & tax", "Financeiro e fiscal"],
196
+ line: ["Cash, bills, banks and invoices.", "Caixa, contas, bancos e nota fiscal."],
197
+ plugins: ["financial", "fiscal-br", "banking-br", "openbanking"]
198
+ },
199
+ {
200
+ id: "clients",
201
+ label: ["Clients & marketing", "Clientes e marketing"],
202
+ line: ["Who your clients are and how you talk to them.", "Quem s\xE3o seus clientes e como voc\xEA fala com eles."],
203
+ plugins: ["crm", "conversations", "marketing", "automations", "reputation"]
204
+ },
205
+ {
206
+ id: "team",
207
+ label: ["Team", "Equipe"],
208
+ line: ["Shifts, time clock and tasks.", "Escala, ponto e tarefas."],
209
+ plugins: ["workforce", "tasks"]
210
+ },
211
+ {
212
+ id: "base",
213
+ label: ["Platform", "Base do sistema"],
214
+ line: ["Registries, reports and what every account has.", "Cadastros, relat\xF3rios e o que toda conta tem."],
215
+ plugins: ["registry", "reports", "dashboard", "notifications", "analytics"]
216
+ }
217
+ ];
218
+ var FAMILY_OF = new Map(MODULE_FAMILIES.flatMap((f) => f.plugins.map((p) => [p, f.id])));
219
+ function moduleFamilyOf(key) {
220
+ return FAMILY_OF.get(key) ?? FAMILY_OF.get(key.split(".")[0]) ?? "base";
221
+ }
222
+ var UPCOMING_MODULE_INFO = {
223
+ tiss: { line: ["Bill health plans in the TISS standard, guide by guide.", "Fature conv\xEAnios no padr\xE3o TISS, guia por guia."] },
224
+ "pet.grooming": { line: ["Booked by pet, priced by size and coat, from drop-off to pick-up.", "Marcado pelo pet, pre\xE7o por porte e pelagem, do recebido ao entregue."], v1: "ThePet \xB7 /pet/servicos" },
225
+ "pet.vet": { line: ["Vet records, vaccines and returns per pet.", "Prontu\xE1rio, vacinas e retornos de cada pet."], v1: "ThePet" },
226
+ "pet.lodging": { line: ["Hotel and daycare stays with check-in and daily log.", "Hospedagem e creche com entrada, sa\xEDda e di\xE1rio."], v1: "ThePet" },
227
+ "pet.packages": { line: ["Weekly bath plans and pick-up & drop-off.", "Planos de banho semanais e leva-e-traz."], v1: "ThePet \xB7 /pet/planos, /pet/leva-e-traz" },
228
+ marketplaces: { line: ["Sell the same catalog on Mercado Livre, Shopee and Amazon.", "Venda o mesmo cat\xE1logo no Mercado Livre, Shopee e Amazon."] }
229
+ };
230
+
231
+ export { MODULE_FAMILIES, UPCOMING_MODULE_INFO, clearAppConfigSnapshot, moduleFamilyOf, useAppConfig };
232
+ //# sourceMappingURL=chunk-G5HNA5QK.js.map
233
+ //# sourceMappingURL=chunk-G5HNA5QK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app/app-config-cache.ts","../src/hooks/useAppConfig.ts","../src/billing/module-families.ts"],"names":["error"],"mappings":"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","file":"chunk-G5HNA5QK.js","sourcesContent":["import type { AppConfig } from '@fayz-ai/core'\n\n/**\n * Snapshot of `app_config()` so a reload paints the tenant's own shell before\n * the network answers.\n *\n * The in-memory cache in @fayz-ai/core survives a route change and dies with\n * the tab, so every reload used to render the app's DEFAULT rail first and\n * correct itself a round trip later — the one flash a tenant with a trimmed\n * menu notices, because the modules they turned off appear and then leave.\n *\n * Same shape as the org snapshot next door (see ../org/cache.ts): versioned,\n * keyed by tenant, dropped when too old, skipped when too big. It is a paint\n * accelerator, never the source of truth — `useAppConfig` always revalidates.\n */\n\nconst STORAGE_KEY = 'fayz:app-config'\nconst VERSION = 1\n\n/** Painted without waiting for the network. Beyond this it is still painted, but as stale. */\nexport const APP_CONFIG_TTL_MS = 5 * 60 * 1000\n/** Older than this and the snapshot is dropped rather than painted. */\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000\n/** A config this big is a defect; skip persisting rather than blow the quota. */\nconst MAX_BYTES = 256 * 1024\n\ninterface Snapshot {\n version: number\n tenantId: string\n savedAt: number\n config: AppConfig\n}\n\nfunction storage(): Storage | null {\n try {\n // The bare global, as ../org/cache.ts reads it: a test stubs `localStorage`\n // and `window` may not exist at all (SSR, a node test).\n return globalThis.localStorage ?? null\n } catch {\n // Private windows and blocked site data throw on ACCESS, not on read.\n return null\n }\n}\n\nconst keyFor = (tenantId: string) => `${STORAGE_KEY}:${tenantId}`\n\n/** The last known config for this tenant, or null. Never throws. */\nexport function readAppConfigSnapshot(tenantId: string): { config: AppConfig; stale: boolean } | null {\n const store = storage()\n if (!store) return null\n try {\n const raw = store.getItem(keyFor(tenantId))\n if (!raw) return null\n const snap = JSON.parse(raw) as Snapshot\n if (snap.version !== VERSION || snap.tenantId !== tenantId) return null\n const age = Date.now() - snap.savedAt\n if (age > MAX_AGE_MS) {\n store.removeItem(keyFor(tenantId))\n return null\n }\n return { config: snap.config, stale: age > APP_CONFIG_TTL_MS }\n } catch {\n return null\n }\n}\n\n/** Records what the server just said. Never throws. */\nexport function writeAppConfigSnapshot(tenantId: string, config: AppConfig): void {\n const store = storage()\n if (!store) return\n try {\n const payload = JSON.stringify({ version: VERSION, tenantId, savedAt: Date.now(), config } satisfies Snapshot)\n if (payload.length > MAX_BYTES) return\n store.setItem(keyFor(tenantId), payload)\n } catch {\n // A quota error must not break a render. The next load is simply cold.\n }\n}\n\n/** Drops the snapshot — after a write through the config RPCs, or on sign-out. */\nexport function clearAppConfigSnapshot(tenantId?: string): void {\n const store = storage()\n if (!store) return\n try {\n if (tenantId) {\n store.removeItem(keyFor(tenantId))\n return\n }\n for (let i = store.length - 1; i >= 0; i -= 1) {\n const key = store.key(i)\n if (key?.startsWith(`${STORAGE_KEY}:`)) store.removeItem(key)\n }\n } catch {\n // Nothing to do: a snapshot we cannot drop is revalidated anyway.\n }\n}\n","import { errorMessage } from '@fayz-ai/core'\nimport { useCallback, useEffect, useLayoutEffect, useMemo } from 'react'\nimport { fetchAppConfig, invalidateAppConfig, toTenantPluginBindings, type AppConfig } from '@fayz-ai/core'\nimport { getSupabaseClientOptional } from '../lib/supabase'\nimport { useOrganizationStore } from '../stores/organization.store'\nimport { useAppConfigStore, activePluginIds, hasPlugin } from '../stores/app-config.store'\nimport { readAppConfigSnapshot, writeAppConfigSnapshot, clearAppConfigSnapshot } from '../app/app-config-cache'\n\n// ---------------------------------------------------------------------------\n// useAppConfig — the shell's single initial load (#280, ADR 0023).\n//\n// One call, one round trip: branding, which plugins and facets are on, the\n// tenant's menu composition, and the caller's permissions. Everything the frame\n// needs before it knows anything else.\n//\n// ── It never throws, and that is the requirement ────────────────────────────\n// A shell that will not render because a config call failed is worse than one\n// that renders defaults. `fetchAppConfig` already guarantees this; the hook\n// keeps the guarantee by treating a failure as \"an empty config, and a note in\n// the store\" rather than as an exception a boundary has to catch.\n//\n// ── Why a refresh does not blank the screen ─────────────────────────────────\n// This drives the frame. Re-reading it after an activation change must leave\n// the previous answer on screen until the new one lands, or every toggle is a\n// flash of empty application.\n// ---------------------------------------------------------------------------\n\nexport interface UseAppConfigResult {\n config: AppConfig\n /** True only until the first answer arrives. */\n initializing: boolean\n /**\n * A configuração em mãos é sobre a conta em que a sessão está.\n *\n * Falso na janela entre escolher outra conta e a resposta dela chegar — a\n * janela em que o shell pintava o rail do bundle e o guarda de rota negava\n * por não ter perfil ainda. Quem renderiza a moldura espera por isto.\n */\n ready: boolean\n stale: boolean\n error: string | null\n /** Active plugin ids, facets folded in. */\n activePlugins: string[]\n /** `has('financial')` for the plugin, `has('financial', 'reconciliation')` for a part. */\n has: (plugin: string, facet?: string) => boolean\n /** What `resolvePluginRuntime` takes — the bridge from config to the runtime. */\n bindings: ReturnType<typeof toTenantPluginBindings>\n /** Re-reads after an activation change. Keeps the current config visible. */\n refresh: () => Promise<void>\n}\n\n/** How often to ask whether the tenant's entitlements moved. One timestamp. */\nconst CONFIG_POLL_MS = 60_000\n\nconst EMPTY = { tenant_id: null, plugins: [], apps: [], nav: [], permissions: [] } as AppConfig\n\nasync function loadFor(tenantId: string | null, force = false): Promise<AppConfig> {\n const supabase = getSupabaseClientOptional()\n // No client is not an error — a storybook, a test, a page rendered before\n // sign-in. An empty config renders the defaults, which is the point.\n if (!supabase) return { ...EMPTY, tenant_id: tenantId }\n return fetchAppConfig(supabase, { tenantId: tenantId ?? undefined, force })\n}\n\nexport function useAppConfig(options?: { enabled?: boolean }): UseAppConfigResult {\n const enabled = options?.enabled !== false\n const currentOrg = useOrganizationStore((state) => state.currentOrg)\n const tenantId = currentOrg?.id ?? null\n\n const config = useAppConfigStore((state) => state.config)\n const configTenantId = useAppConfigStore((state) => state.tenantId)\n const beginTenant = useAppConfigStore((state) => state.beginTenant)\n const initializing = useAppConfigStore((state) => state.initializing)\n const stale = useAppConfigStore((state) => state.stale)\n const error = useAppConfigStore((state) => state.error)\n const setConfig = useAppConfigStore((state) => state.setConfig)\n const setStale = useAppConfigStore((state) => state.setStale)\n const setError = useAppConfigStore((state) => state.setError)\n const reset = useAppConfigStore((state) => state.reset)\n\n const refresh = useCallback(async () => {\n setStale(true)\n try {\n if (tenantId) {\n invalidateAppConfig(tenantId)\n clearAppConfigSnapshot(tenantId)\n }\n const next = await loadFor(tenantId, true)\n if (tenantId) writeAppConfigSnapshot(tenantId, next)\n setConfig(next, tenantId)\n } catch (err) {\n setError(errorMessage(err))\n } finally {\n setStale(false)\n }\n }, [tenantId, setConfig, setError, setStale])\n\n // A conta mudou: o que está em tela não é sobre ela.\n //\n // Antes desta marca o shell seguia com a configuração anterior (ou o padrão\n // do bundle) até a resposta nova chegar, e então trocava tudo de uma vez —\n // o rail curto e o \"acesso restrito\" que piscavam na troca de conta.\n useLayoutEffect(() => {\n if (!enabled) return\n if (configTenantId !== tenantId) beginTenant(tenantId)\n }, [enabled, tenantId, configTenantId, beginTenant])\n\n // Paint the tenant's own shell before the network answers.\n //\n // In a layout effect, not a plain one: the rail is composed during render, so\n // an effect that lands after paint shows the app's DEFAULT menu for a frame\n // and corrects it — which is exactly the flash a tenant with modules turned\n // off notices, because the ones they removed appear and then leave.\n useLayoutEffect(() => {\n if (!enabled || !tenantId) return\n const snap = readAppConfigSnapshot(tenantId)\n if (!snap) return\n setConfig(snap.config, tenantId)\n if (snap.stale) setStale(true)\n }, [enabled, tenantId, setConfig, setStale])\n\n useEffect(() => {\n if (!enabled) {\n reset()\n return\n }\n let cancelled = false\n void (async () => {\n try {\n const next = await loadFor(tenantId)\n if (cancelled) return\n if (tenantId) writeAppConfigSnapshot(tenantId, next)\n setConfig(next, tenantId)\n setStale(false)\n } catch (err) {\n // fetchAppConfig does not throw; this catches a client that does.\n if (!cancelled) setError(errorMessage(err))\n }\n })()\n return () => {\n cancelled = true\n }\n }, [enabled, tenantId, setConfig, setError, setStale, reset])\n\n // A plan change has to reach the screen without a reload (#281, ADR 0025).\n //\n // Polling `app_config()` would work and would be wasteful — it returns\n // branding, every activation, the whole menu. `tenant_config_version()`\n // returns one timestamp, so the common case (nothing changed) costs a number,\n // and the config is re-read only when it actually moved. An upgrade taken on\n // the billing page lights up the module on this one within a minute, and a\n // downgrade takes it away just as quickly.\n useEffect(() => {\n if (!enabled || !tenantId) return\n const supabase = getSupabaseClientOptional()\n if (!supabase) return\n let cancelled = false\n let seen: string | null = null\n\n const poll = async () => {\n try {\n const { data, error } = await supabase.rpc('tenant_config_version')\n if (cancelled || error || !data) return\n const stamp = String(data)\n if (seen === null) {\n seen = stamp\n return\n }\n if (stamp !== seen) {\n seen = stamp\n invalidateAppConfig(tenantId)\n const next = await loadFor(tenantId, true)\n writeAppConfigSnapshot(tenantId, next)\n setConfig(next, tenantId)\n }\n } catch {\n // A poll that fails changes nothing on screen, which is the correct\n // outcome: the config already rendered is still the last good answer.\n }\n }\n\n const timer = setInterval(() => void poll(), CONFIG_POLL_MS)\n void poll()\n return () => {\n cancelled = true\n clearInterval(timer)\n }\n }, [enabled, tenantId, setConfig])\n\n // Derived per config, not per render. `bindings` feeds the dependency array of\n // the plugin runtime: a fresh array on every render would re-resolve the whole\n // runtime on every render and remount every plugin page under it.\n const activePlugins = useMemo(() => activePluginIds(config), [config])\n const bindings = useMemo(() => toTenantPluginBindings(config), [config])\n const has = useCallback(\n (plugin: string, facet?: string) => hasPlugin(config, plugin, facet),\n [config],\n )\n\n // Sem conta escolhida não há o que esperar: a tela de escolha é a própria\n // resposta. Com conta, `ready` só é verdade quando a configuração em mãos é a\n // dela.\n const ready = !tenantId || (configTenantId === tenantId && !initializing)\n\n return { config, initializing, ready, stale, error, activePlugins, has, bindings, refresh }\n}\n\n","import { tl } from '@fayz-ai/core'\n\n/**\n * Families of modules — how Módulos (and anything else that lists modules, the\n * price list included) groups them. Keyed by plugin id; a facet follows its\n * plugin. A module not listed falls in \"Base do sistema\".\n */\nexport interface ModuleFamily {\n id: string\n label: readonly [string, string]\n /** One line under the heading: what this family is for. */\n line: readonly [string, string]\n plugins: readonly string[]\n}\n\nexport const MODULE_FAMILIES: readonly ModuleFamily[] = [\n { id: 'sales', label: ['Sales & floor', 'Vendas e salão'], line: ['Selling at the counter, the table and online.', 'Vender no balcão, na mesa e online.'],\n plugins: ['orders', 'tables', 'menu', 'reservations', 'shop', 'payments', 'sites', 'marketplaces'] },\n { id: 'stock', label: ['Stock & production', 'Estoque e produção'], line: ['What comes in, what is made, what goes out.', 'O que entra, o que se produz, o que sai.'],\n plugins: ['inventory', 'production', 'kitchen'] },\n { id: 'care', label: ['Attendance & schedule', 'Atendimento e agenda'], line: ['Bookings, records and the service itself.', 'Agendamento, prontuário e o atendimento em si.'],\n plugins: ['agenda', 'clinical', 'forms', 'custom_forms', 'scribe', 'courses', 'tiss', 'pet'] },\n { id: 'finance', label: ['Finance & tax', 'Financeiro e fiscal'], line: ['Cash, bills, banks and invoices.', 'Caixa, contas, bancos e nota fiscal.'],\n plugins: ['financial', 'fiscal-br', 'banking-br', 'openbanking'] },\n { id: 'clients', label: ['Clients & marketing', 'Clientes e marketing'], line: ['Who your clients are and how you talk to them.', 'Quem são seus clientes e como você fala com eles.'],\n plugins: ['crm', 'conversations', 'marketing', 'automations', 'reputation'] },\n { id: 'team', label: ['Team', 'Equipe'], line: ['Shifts, time clock and tasks.', 'Escala, ponto e tarefas.'],\n plugins: ['workforce', 'tasks'] },\n { id: 'base', label: ['Platform', 'Base do sistema'], line: ['Registries, reports and what every account has.', 'Cadastros, relatórios e o que toda conta tem.'],\n plugins: ['registry', 'reports', 'dashboard', 'notifications', 'analytics'] },\n]\n\nconst FAMILY_OF = new Map(MODULE_FAMILIES.flatMap((f) => f.plugins.map((p) => [p, f.id] as const)))\n\n/** The family of a module or facet key (`plugin` / `plugin.facet`). */\nexport function moduleFamilyOf(key: string): string {\n return FAMILY_OF.get(key) ?? FAMILY_OF.get(key.split('.')[0]!) ?? 'base'\n}\n\nexport function familyLabel(id: string): string {\n const f = MODULE_FAMILIES.find((x) => x.id === id)\n return f ? tl(...f.label) : id\n}\n\n/**\n * What each coming-soon module (`COMING_SOON_MODULES` in the price list) is\n * for, and where it came from. Data only: none of these registers a route.\n */\nexport const UPCOMING_MODULE_INFO: Readonly<Record<string, { line: readonly [string, string]; v1?: string }>> = {\n tiss: { line: ['Bill health plans in the TISS standard, guide by guide.', 'Fature convênios no padrão TISS, guia por guia.'] },\n 'pet.grooming': { line: ['Booked by pet, priced by size and coat, from drop-off to pick-up.', 'Marcado pelo pet, preço por porte e pelagem, do recebido ao entregue.'], v1: 'ThePet · /pet/servicos' },\n 'pet.vet': { line: ['Vet records, vaccines and returns per pet.', 'Prontuário, vacinas e retornos de cada pet.'], v1: 'ThePet' },\n 'pet.lodging': { line: ['Hotel and daycare stays with check-in and daily log.', 'Hospedagem e creche com entrada, saída e diário.'], v1: 'ThePet' },\n 'pet.packages': { line: ['Weekly bath plans and pick-up & drop-off.', 'Planos de banho semanais e leva-e-traz.'], v1: 'ThePet · /pet/planos, /pet/leva-e-traz' },\n marketplaces: { line: ['Sell the same catalog on Mercado Livre, Shopee and Amazon.', 'Venda o mesmo catálogo no Mercado Livre, Shopee e Amazon.'] },\n}\n"]}
@@ -0,0 +1,53 @@
1
+ import { cn } from './chunk-JRZJBMOY.js';
2
+ import { Sparkles, Check } from 'lucide-react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/components/billing/access-contract.ts
6
+ function getPlanEntitlements(plan) {
7
+ return plan?.entitlements;
8
+ }
9
+ function featureDisplayName(label) {
10
+ if (!label) return "";
11
+ return label.replace(/\s*\([^)]*\)\s*$/, "").trim() || label.trim();
12
+ }
13
+ var CURRENCY_BY_REGION = {
14
+ BR: "BRL",
15
+ PT: "EUR",
16
+ ES: "EUR",
17
+ FR: "EUR",
18
+ DE: "EUR",
19
+ IT: "EUR",
20
+ NL: "EUR",
21
+ IE: "EUR",
22
+ GB: "GBP",
23
+ US: "USD",
24
+ CA: "CAD",
25
+ AU: "AUD",
26
+ MX: "MXN",
27
+ AR: "ARS",
28
+ CL: "CLP",
29
+ CO: "COP"
30
+ };
31
+ function resolvePlanCurrency(planCurrency, locale) {
32
+ if (planCurrency) return planCurrency;
33
+ const region = locale?.split(/[-_]/)[1]?.toUpperCase();
34
+ return region && CURRENCY_BY_REGION[region] || "USD";
35
+ }
36
+ function parsePlanFeature(feature) {
37
+ return feature.startsWith("*") ? { label: feature.slice(1).trim(), highlighted: true } : { label: feature, highlighted: false };
38
+ }
39
+ function PlanFeatureItem({ feature, className, iconClassName }) {
40
+ const { label, highlighted } = parsePlanFeature(feature);
41
+ return /* @__PURE__ */ jsxs("li", { className: cn(
42
+ "flex items-start gap-2",
43
+ className,
44
+ highlighted && "-mx-1.5 rounded-md bg-violet-50 px-1.5 py-1 font-medium text-violet-700 dark:bg-violet-950/60 dark:text-violet-200"
45
+ ), children: [
46
+ highlighted ? /* @__PURE__ */ jsx(Sparkles, { className: cn("mt-0.5 shrink-0 text-violet-600 dark:text-violet-300", iconClassName), "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Check, { className: cn("mt-0.5 shrink-0 text-primary", iconClassName), "aria-hidden": "true" }),
47
+ /* @__PURE__ */ jsx("span", { children: label })
48
+ ] });
49
+ }
50
+
51
+ export { PlanFeatureItem, featureDisplayName, getPlanEntitlements, parsePlanFeature, resolvePlanCurrency };
52
+ //# sourceMappingURL=chunk-HCFO4Z6A.js.map
53
+ //# sourceMappingURL=chunk-HCFO4Z6A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/billing/access-contract.ts","../src/components/billing/plan-feature.tsx"],"names":[],"mappings":";;;;;AA0BO,SAAS,oBAAoB,IAAA,EAA6D;AAC/F,EAAA,OAAO,IAAA,EAAM,YAAA;AACf;AAUO,SAAS,mBAAmB,KAAA,EAA0C;AAC3E,EAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AACnB,EAAA,OAAO,KAAA,CAAM,QAAQ,kBAAA,EAAoB,EAAE,EAAE,IAAA,EAAK,IAAK,MAAM,IAAA,EAAK;AACpE;AASA,IAAM,kBAAA,GAA6C;AAAA,EACjD,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EACjF,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI,KAAA;AAAA,EAAO,EAAA,EAAI;AACnF,CAAA;AAEO,SAAS,mBAAA,CAAoB,cAAkC,MAAA,EAAoC;AACxG,EAAA,IAAI,cAAc,OAAO,YAAA;AACzB,EAAA,MAAM,SAAS,MAAA,EAAQ,KAAA,CAAM,MAAM,CAAA,CAAE,CAAC,GAAG,WAAA,EAAY;AACrD,EAAA,OAAQ,MAAA,IAAU,kBAAA,CAAmB,MAAM,CAAA,IAAM,KAAA;AACnD;AClDO,SAAS,iBAAiB,OAAA,EAA0D;AACzF,EAAA,OAAO,QAAQ,UAAA,CAAW,GAAG,IACzB,EAAE,KAAA,EAAO,QAAQ,KAAA,CAAM,CAAC,EAAE,IAAA,EAAK,EAAG,aAAa,IAAA,EAAK,GACpD,EAAE,KAAA,EAAO,OAAA,EAAS,aAAa,KAAA,EAAM;AAC3C;AAEO,SAAS,eAAA,CAAgB,EAAE,OAAA,EAAS,SAAA,EAAW,eAAc,EAIjE;AACD,EAAA,MAAM,EAAE,KAAA,EAAO,WAAA,EAAY,GAAI,iBAAiB,OAAO,CAAA;AACvD,EAAA,uBACE,IAAA,CAAC,QAAG,SAAA,EAAW,EAAA;AAAA,IAAG,wBAAA;AAAA,IAA0B,SAAA;AAAA,IAC1C,WAAA,IAAe;AAAA,GAAoH,EAClI,QAAA,EAAA;AAAA,IAAA,WAAA,uBACE,QAAA,EAAA,EAAS,SAAA,EAAW,GAAG,sDAAA,EAAwD,aAAa,GAAG,aAAA,EAAY,MAAA,EAAO,CAAA,mBAEnH,GAAA,CAAC,SAAM,SAAA,EAAW,EAAA,CAAG,gCAAgC,aAAa,CAAA,EAAG,eAAY,MAAA,EAAO,CAAA;AAAA,oBAE1F,GAAA,CAAC,UAAM,QAAA,EAAA,KAAA,EAAM;AAAA,GAAA,EACf,CAAA;AAEJ","file":"chunk-HCFO4Z6A.js","sourcesContent":["// ---------------------------------------------------------------------------\n// access-contract — the single import seam the billing UI uses for the\n// entitlements engine (UpgradePrompt, UpgradeModal, EntitlementGate, LimitGate,\n// SoftLimitWidget, the AdminShell route guard / nav badge, SubscriptionPage\n// matrix). Re-exports the core access engine (packages/saas/src/access) so the\n// billing layer has ONE stable path and the wiring (AccessProvider) stays in the\n// núcleo. `getPlanEntitlements` is a thin local reader — core's Plan carries the\n// entitlements, so this just narrows the access.\n//\n// (History: this file shipped as a temporary stub while the access engine landed\n// in parallel; now repointed to the real module. The public names/types below\n// match the fixed contract, so no billing-wave consumer changed.)\n// ---------------------------------------------------------------------------\nimport type { Plan, PlanEntitlements } from '@fayz-ai/core'\n\nexport { useAccessOptional, useLimit, useLimitGuard } from '../../access/context'\nexport { invalidateLimit } from '../../access/limits-registry'\nexport { useUpgradeModalStore } from '../../access/upgrade-modal-store'\n\nexport type { AccessApi, AccessDecision } from '../../access/types'\n// The billing layer refers to the limit shape as `LimitInfo`; the engine calls\n// it `LimitState`. Same structure — alias to keep call sites stable.\nexport type { LimitState as LimitInfo } from '../../access/types'\nexport type { PlanEntitlements } from '@fayz-ai/core'\n\n/** Narrow a plan to its entitlements (undefined when the plan declares none). */\nexport function getPlanEntitlements(plan: Plan | undefined | null): PlanEntitlements | undefined {\n return plan?.entitlements\n}\n\n/**\n * Clean a feature's raw permission label into a display name for the upgrade\n * surfaces. Feature labels in an app's permissions config carry an access-scope\n * suffix — e.g. \"Marketing (acesso)\" / \"Reports (access)\" — which reads as noise\n * when interpolated into a headline (\"Unlock Marketing (acesso)\"). Strip a single\n * trailing parenthetical group and collapse surrounding whitespace. Shared by\n * UpgradePrompt AND UpgradeModal so the two surfaces stay in lockstep.\n */\nexport function featureDisplayName(label: string | null | undefined): string {\n if (!label) return ''\n return label.replace(/\\s*\\([^)]*\\)\\s*$/, '').trim() || label.trim()\n}\n\n/**\n * Which ISO-4217 code to price a plan in. `Plan.currency` is the source of\n * truth, but apps DO ship billing configs that omit it — and silently printing\n * \"$79\" to a tenant billed in reais is worse than any other failure mode here.\n * So the fallback follows the ACTIVE LOCALE's region rather than a hardcoded\n * 'USD'; only a locale we can't place at all lands on USD.\n */\nconst CURRENCY_BY_REGION: Record<string, string> = {\n BR: 'BRL', PT: 'EUR', ES: 'EUR', FR: 'EUR', DE: 'EUR', IT: 'EUR', NL: 'EUR', IE: 'EUR',\n GB: 'GBP', US: 'USD', CA: 'CAD', AU: 'AUD', MX: 'MXN', AR: 'ARS', CL: 'CLP', CO: 'COP',\n}\n\nexport function resolvePlanCurrency(planCurrency: string | undefined, locale: string | undefined): string {\n if (planCurrency) return planCurrency\n const region = locale?.split(/[-_]/)[1]?.toUpperCase()\n return (region && CURRENCY_BY_REGION[region]) || 'USD'\n}\n","import * as React from 'react'\nimport { Check, Sparkles } from 'lucide-react'\nimport { cn } from '../../lib/cn'\n\n// A plan feature string prefixed with `*` renders highlighted (Sparkles on a\n// faint violet chip) — the config-side seam for \"hero\" features like the AI\n// assistant. Shared by UpgradePrompt, PlanSelector and the pricing cards.\n// violet-700 on violet-50 and violet-200 on violet-950 both clear AA (≥ 6:1).\n\nexport function parsePlanFeature(feature: string): { label: string; highlighted: boolean } {\n return feature.startsWith('*')\n ? { label: feature.slice(1).trim(), highlighted: true }\n : { label: feature, highlighted: false }\n}\n\nexport function PlanFeatureItem({ feature, className, iconClassName }: {\n feature: string\n className?: string\n iconClassName?: string\n}) {\n const { label, highlighted } = parsePlanFeature(feature)\n return (\n <li className={cn('flex items-start gap-2', className,\n highlighted && '-mx-1.5 rounded-md bg-violet-50 px-1.5 py-1 font-medium text-violet-700 dark:bg-violet-950/60 dark:text-violet-200')}>\n {highlighted ? (\n <Sparkles className={cn('mt-0.5 shrink-0 text-violet-600 dark:text-violet-300', iconClassName)} aria-hidden=\"true\" />\n ) : (\n <Check className={cn('mt-0.5 shrink-0 text-primary', iconClassName)} aria-hidden=\"true\" />\n )}\n <span>{label}</span>\n </li>\n )\n}\n"]}
@@ -0,0 +1,51 @@
1
+ import { useRightRailHosted, useRightRailStore, useRightRailPageDrill } from './chunk-PQ47MLTH.js';
2
+ import * as React from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { Sheet, SheetContent } from '@fayz-ai/ui';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ function RightRailPage({
8
+ title,
9
+ open,
10
+ onClose,
11
+ width,
12
+ children
13
+ }) {
14
+ const hosted = useRightRailHosted();
15
+ const slot = useRightRailStore((s) => s.pageSlot);
16
+ const showPage = useRightRailStore((s) => s.showPage);
17
+ const closePage = useRightRailStore((s) => s.closePage);
18
+ const active = hosted && open;
19
+ const pageOpen = useRightRailStore((s) => s.pageOpen);
20
+ const onCloseRef = React.useRef(onClose);
21
+ onCloseRef.current = onClose;
22
+ const claimed = React.useRef(false);
23
+ React.useEffect(() => {
24
+ if (!active) return;
25
+ showPage();
26
+ claimed.current = true;
27
+ return () => {
28
+ claimed.current = false;
29
+ closePage();
30
+ };
31
+ }, [active, showPage, closePage]);
32
+ React.useEffect(() => {
33
+ if (!claimed.current) return;
34
+ if (!useRightRailStore.getState().pageOpen) onCloseRef.current();
35
+ }, [pageOpen]);
36
+ useRightRailPageDrill(active ? { title, back: onClose } : null);
37
+ if (!open) return null;
38
+ if (hosted) {
39
+ return slot ? createPortal(
40
+ /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden", children }),
41
+ slot
42
+ ) : null;
43
+ }
44
+ return /* @__PURE__ */ jsx(Sheet, { open: true, onOpenChange: (next) => {
45
+ if (!next) onClose();
46
+ }, children: /* @__PURE__ */ jsx(SheetContent, { width: width ?? "max-w-md", children }) });
47
+ }
48
+
49
+ export { RightRailPage };
50
+ //# sourceMappingURL=chunk-JFIU6NQP.js.map
51
+ //# sourceMappingURL=chunk-JFIU6NQP.js.map