@fayz-ai/saas 0.8.1 → 0.8.2

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 (233) hide show
  1. package/dist/access/context.d.ts +47 -0
  2. package/dist/access/context.d.ts.map +1 -0
  3. package/dist/access/headless.d.ts +21 -0
  4. package/dist/access/headless.d.ts.map +1 -0
  5. package/dist/access/index.d.ts +12 -0
  6. package/dist/access/index.d.ts.map +1 -0
  7. package/dist/access/limits-registry.d.ts +33 -0
  8. package/dist/access/limits-registry.d.ts.map +1 -0
  9. package/dist/access/module-nav.d.ts +15 -0
  10. package/dist/access/module-nav.d.ts.map +1 -0
  11. package/dist/access/resolver.d.ts +2 -0
  12. package/dist/access/resolver.d.ts.map +1 -0
  13. package/dist/access/types.d.ts +43 -0
  14. package/dist/access/types.d.ts.map +1 -0
  15. package/dist/access/upgrade-modal-store.d.ts +23 -0
  16. package/dist/access/upgrade-modal-store.d.ts.map +1 -0
  17. package/dist/app/AdminShell.d.ts +3 -0
  18. package/dist/app/AdminShell.d.ts.map +1 -1
  19. package/dist/app/admin-app.d.ts +3 -1
  20. package/dist/app/admin-app.d.ts.map +1 -1
  21. package/dist/app/config.d.ts +47 -2
  22. package/dist/app/config.d.ts.map +1 -1
  23. package/dist/app/core-entities.d.ts +25 -0
  24. package/dist/app/core-entities.d.ts.map +1 -0
  25. package/dist/app/derive-contract.d.ts +8 -0
  26. package/dist/app/derive-contract.d.ts.map +1 -0
  27. package/dist/app/scaffold.d.ts.map +1 -1
  28. package/dist/archetype-lookup.d.ts.map +1 -1
  29. package/dist/billing/index.cjs +6 -2
  30. package/dist/billing/index.d.ts +2 -0
  31. package/dist/billing/index.d.ts.map +1 -1
  32. package/dist/billing/index.js +1 -1
  33. package/dist/billing/plan-badge.d.ts +17 -0
  34. package/dist/billing/plan-badge.d.ts.map +1 -0
  35. package/dist/billing/store.d.ts +6 -1
  36. package/dist/billing/store.d.ts.map +1 -1
  37. package/dist/{chunk-3M25GRRU.js → chunk-I3DY47N5.js} +3 -4
  38. package/dist/chunk-I3DY47N5.js.map +1 -0
  39. package/dist/chunk-NNFGMUF2.cjs +39 -0
  40. package/dist/chunk-NNFGMUF2.cjs.map +1 -0
  41. package/dist/{chunk-4XTQ4V5L.cjs → chunk-SAZLW54Y.cjs} +16 -12
  42. package/dist/chunk-SAZLW54Y.cjs.map +1 -0
  43. package/dist/{chunk-IPWCHJPE.cjs → chunk-VKYZACG5.cjs} +5 -6
  44. package/dist/chunk-VKYZACG5.cjs.map +1 -0
  45. package/dist/chunk-VN5NTK26.js +36 -0
  46. package/dist/chunk-VN5NTK26.js.map +1 -0
  47. package/dist/{chunk-XNWCGJYH.js → chunk-VTEP2AQX.js} +15 -11
  48. package/dist/chunk-VTEP2AQX.js.map +1 -0
  49. package/dist/components/shared/ContactPicker.d.ts +92 -0
  50. package/dist/components/shared/ContactPicker.d.ts.map +1 -0
  51. package/dist/components/shared/PersonLink.d.ts +12 -1
  52. package/dist/components/shared/PersonLink.d.ts.map +1 -1
  53. package/dist/components/shared/create-person.d.ts +20 -0
  54. package/dist/components/shared/create-person.d.ts.map +1 -0
  55. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  56. package/dist/crud/CrudFormPage.d.ts.map +1 -1
  57. package/dist/crud/CrudListView.d.ts.map +1 -1
  58. package/dist/crud/CrudPage.d.ts.map +1 -1
  59. package/dist/crud/archetypes/AddressesTab.d.ts +21 -0
  60. package/dist/crud/archetypes/AddressesTab.d.ts.map +1 -0
  61. package/dist/crud/archetypes/PersonDetailTabs.d.ts +2 -1
  62. package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -1
  63. package/dist/crud/createCrudPage.d.ts.map +1 -1
  64. package/dist/crud/index.d.ts +1 -0
  65. package/dist/crud/index.d.ts.map +1 -1
  66. package/dist/index.cjs +4281 -716
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.ts +17 -2
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4067 -542
  71. package/dist/index.js.map +1 -1
  72. package/dist/org/index.cjs +11 -11
  73. package/dist/org/index.js +2 -2
  74. package/dist/org/store.d.ts.map +1 -1
  75. package/dist/permissions/context.d.ts +2 -0
  76. package/dist/permissions/context.d.ts.map +1 -1
  77. package/dist/permissions/index.cjs +7 -7
  78. package/dist/permissions/index.js +1 -1
  79. package/dist/shell/components/auth/ProtectedRoute.d.ts.map +1 -1
  80. package/dist/shell/components/billing/SoftLimitBanner.d.ts +5 -0
  81. package/dist/shell/components/billing/SoftLimitBanner.d.ts.map +1 -0
  82. package/dist/shell/components/billing/SubscriptionPage.d.ts +5 -0
  83. package/dist/shell/components/billing/SubscriptionPage.d.ts.map +1 -0
  84. package/dist/shell/components/billing/UpgradeModal.d.ts +3 -0
  85. package/dist/shell/components/billing/UpgradeModal.d.ts.map +1 -0
  86. package/dist/shell/components/billing/UpgradePrompt.d.ts +10 -0
  87. package/dist/shell/components/billing/UpgradePrompt.d.ts.map +1 -0
  88. package/dist/shell/components/billing/access-contract.d.ts +18 -0
  89. package/dist/shell/components/billing/access-contract.d.ts.map +1 -0
  90. package/dist/shell/components/billing/gates.d.ts +14 -0
  91. package/dist/shell/components/billing/gates.d.ts.map +1 -0
  92. package/dist/shell/components/billing/index.d.ts +9 -2
  93. package/dist/shell/components/billing/index.d.ts.map +1 -1
  94. package/dist/shell/components/chat/ChatFab.d.ts +3 -1
  95. package/dist/shell/components/chat/ChatFab.d.ts.map +1 -1
  96. package/dist/shell/components/chat/ChatPanel.d.ts +3 -1
  97. package/dist/shell/components/chat/ChatPanel.d.ts.map +1 -1
  98. package/dist/shell/components/chat/ConfirmActionCard.d.ts +13 -0
  99. package/dist/shell/components/chat/ConfirmActionCard.d.ts.map +1 -0
  100. package/dist/shell/components/chat/markdown.d.ts +5 -0
  101. package/dist/shell/components/chat/markdown.d.ts.map +1 -0
  102. package/dist/shell/components/organization/TeamTab.d.ts.map +1 -1
  103. package/dist/shell/components/organization/TenantOnboarding.d.ts.map +1 -1
  104. package/dist/shell/components/organization/index.d.ts +0 -1
  105. package/dist/shell/components/organization/index.d.ts.map +1 -1
  106. package/dist/shell/components/settings/ConnectedCompanySettings.d.ts.map +1 -1
  107. package/dist/shell/components/settings/ConnectedFieldRulesSettings.d.ts.map +1 -1
  108. package/dist/shell/components/settings/ConnectedSecuritySettings.d.ts.map +1 -1
  109. package/dist/shell/components/settings/LocationsCrudPage.d.ts +1 -1
  110. package/dist/shell/components/settings/LocationsCrudPage.d.ts.map +1 -1
  111. package/dist/shell/components/settings/SettingsPage.d.ts +23 -2
  112. package/dist/shell/components/settings/SettingsPage.d.ts.map +1 -1
  113. package/dist/shell/hooks/index.d.ts +3 -2
  114. package/dist/shell/hooks/index.d.ts.map +1 -1
  115. package/dist/shell/hooks/useAITools.d.ts +2 -0
  116. package/dist/shell/hooks/useAITools.d.ts.map +1 -1
  117. package/dist/shell/hooks/useChat.d.ts +22 -0
  118. package/dist/shell/hooks/useChat.d.ts.map +1 -1
  119. package/dist/shell/hooks/useSystemPermission.d.ts +3 -0
  120. package/dist/shell/hooks/useSystemPermission.d.ts.map +1 -0
  121. package/dist/shell/hooks/useTenantPluginSettings.d.ts +12 -0
  122. package/dist/shell/hooks/useTenantPluginSettings.d.ts.map +1 -0
  123. package/dist/shell/lib/ai-tool-handlers.d.ts +119 -0
  124. package/dist/shell/lib/ai-tool-handlers.d.ts.map +1 -0
  125. package/dist/shell/lib/core-ai-tools.d.ts +14 -0
  126. package/dist/shell/lib/core-ai-tools.d.ts.map +1 -1
  127. package/dist/shell/lib/create-client-orders-provider.d.ts.map +1 -1
  128. package/dist/shell/lib/fayz-agent.d.ts +23 -0
  129. package/dist/shell/lib/fayz-agent.d.ts.map +1 -0
  130. package/dist/shell/lib/i18n.d.ts.map +1 -1
  131. package/dist/shell/stores/chat.store.d.ts +69 -0
  132. package/dist/shell/stores/chat.store.d.ts.map +1 -1
  133. package/dist/shell/types/billing.d.ts +7 -0
  134. package/dist/shell/types/billing.d.ts.map +1 -1
  135. package/dist/shell/types/plugins.d.ts +2 -45
  136. package/dist/shell/types/plugins.d.ts.map +1 -1
  137. package/dist/stores/createCrudStore.d.ts.map +1 -1
  138. package/package.json +14 -11
  139. package/src/access/context.tsx +228 -0
  140. package/src/access/headless.ts +71 -0
  141. package/src/access/index.ts +27 -0
  142. package/src/access/limits-registry.ts +86 -0
  143. package/src/access/module-nav.test.ts +78 -0
  144. package/src/access/module-nav.ts +82 -0
  145. package/src/access/resolver.test.ts +143 -0
  146. package/src/access/resolver.ts +5 -0
  147. package/src/access/types.ts +47 -0
  148. package/src/access/upgrade-modal-store.ts +30 -0
  149. package/src/app/AdminShell.tsx +451 -68
  150. package/src/app/admin-app.tsx +46 -9
  151. package/src/app/config.ts +54 -1
  152. package/src/app/core-entities.ts +110 -0
  153. package/src/app/derive-contract.test.ts +155 -0
  154. package/src/app/derive-contract.ts +214 -0
  155. package/src/app/scaffold.tsx +4 -0
  156. package/src/archetype-lookup.ts +8 -2
  157. package/src/billing/index.ts +2 -0
  158. package/src/billing/plan-badge.ts +32 -0
  159. package/src/billing/store.ts +8 -1
  160. package/src/components/shared/ContactPicker.tsx +394 -0
  161. package/src/components/shared/PersonLink.tsx +11 -3
  162. package/src/components/shared/create-person.ts +74 -0
  163. package/src/crud/CrudDetailPage.tsx +5 -1
  164. package/src/crud/CrudFormPage.tsx +113 -6
  165. package/src/crud/CrudListView.tsx +16 -1
  166. package/src/crud/CrudPage.tsx +27 -5
  167. package/src/crud/DeleteConfirmDialog.tsx +1 -1
  168. package/src/crud/archetypes/AddressesTab.tsx +387 -0
  169. package/src/crud/archetypes/PersonDetailTabs.tsx +5 -0
  170. package/src/crud/createCrudPage.tsx +2 -0
  171. package/src/crud/index.ts +6 -0
  172. package/src/env.d.ts +7 -0
  173. package/src/index.ts +49 -1
  174. package/src/org/store.ts +9 -1
  175. package/src/permissions/context.tsx +29 -15
  176. package/src/shell/components/auth/ProtectedRoute.tsx +12 -2
  177. package/src/shell/components/billing/SoftLimitBanner.tsx +81 -0
  178. package/src/shell/components/billing/SubscriptionPage.tsx +372 -0
  179. package/src/shell/components/billing/UpgradeModal.tsx +102 -0
  180. package/src/shell/components/billing/UpgradePrompt.tsx +229 -0
  181. package/src/shell/components/billing/access-contract.ts +64 -0
  182. package/src/shell/components/billing/gates.tsx +62 -0
  183. package/src/shell/components/billing/index.ts +18 -2
  184. package/src/shell/components/chat/ChatFab.tsx +4 -2
  185. package/src/shell/components/chat/ChatPanel.tsx +174 -23
  186. package/src/shell/components/chat/ConfirmActionCard.tsx +57 -0
  187. package/src/shell/components/chat/markdown.tsx +102 -0
  188. package/src/shell/components/organization/TeamTab.tsx +21 -4
  189. package/src/shell/components/organization/TenantOnboarding.tsx +3 -1
  190. package/src/shell/components/organization/index.ts +0 -1
  191. package/src/shell/components/settings/ConnectedCompanySettings.tsx +4 -1
  192. package/src/shell/components/settings/ConnectedFieldRulesSettings.tsx +3 -1
  193. package/src/shell/components/settings/ConnectedSecuritySettings.tsx +3 -1
  194. package/src/shell/components/settings/LocationsCrudPage.ts +6 -1
  195. package/src/shell/components/settings/SettingsPage.tsx +63 -21
  196. package/src/shell/hooks/index.ts +3 -2
  197. package/src/shell/hooks/useAITools.ts +27 -23
  198. package/src/shell/hooks/useChat.ts +381 -16
  199. package/src/shell/hooks/useSystemPermission.ts +23 -0
  200. package/src/shell/hooks/useTenantPluginSettings.ts +127 -0
  201. package/src/shell/lib/ai-tool-handlers.ts +910 -0
  202. package/src/shell/lib/core-ai-tools.ts +234 -2
  203. package/src/shell/lib/create-client-orders-provider.ts +24 -2
  204. package/src/shell/lib/fayz-agent.ts +56 -0
  205. package/src/shell/lib/i18n.ts +71 -0
  206. package/src/shell/stores/chat.store.ts +118 -1
  207. package/src/shell/styles.css +30 -0
  208. package/src/shell/types/billing.ts +8 -0
  209. package/src/shell/types/plugins.ts +13 -50
  210. package/src/stores/createCrudStore.ts +9 -0
  211. package/dist/chunk-3M25GRRU.js.map +0 -1
  212. package/dist/chunk-4XTQ4V5L.cjs.map +0 -1
  213. package/dist/chunk-5GYQK5IW.js +0 -21
  214. package/dist/chunk-5GYQK5IW.js.map +0 -1
  215. package/dist/chunk-IPWCHJPE.cjs.map +0 -1
  216. package/dist/chunk-KYID2MR6.cjs +0 -23
  217. package/dist/chunk-KYID2MR6.cjs.map +0 -1
  218. package/dist/chunk-XNWCGJYH.js.map +0 -1
  219. package/dist/shell/components/billing/BillingPage.d.ts +0 -7
  220. package/dist/shell/components/billing/BillingPage.d.ts.map +0 -1
  221. package/dist/shell/components/billing/PaywallGate.d.ts +0 -11
  222. package/dist/shell/components/billing/PaywallGate.d.ts.map +0 -1
  223. package/dist/shell/components/organization/PermissionGate.d.ts +0 -11
  224. package/dist/shell/components/organization/PermissionGate.d.ts.map +0 -1
  225. package/dist/shell/hooks/useBilling.d.ts +0 -14
  226. package/dist/shell/hooks/useBilling.d.ts.map +0 -1
  227. package/dist/shell/hooks/usePermission.d.ts +0 -16
  228. package/dist/shell/hooks/usePermission.d.ts.map +0 -1
  229. package/src/shell/components/billing/BillingPage.tsx +0 -56
  230. package/src/shell/components/billing/PaywallGate.tsx +0 -80
  231. package/src/shell/components/organization/PermissionGate.tsx +0 -16
  232. package/src/shell/hooks/useBilling.ts +0 -151
  233. package/src/shell/hooks/usePermission.ts +0 -101
@@ -3,6 +3,7 @@ import { Clock, FileText, CalendarDays } from 'lucide-react'
3
3
  import { Card, CardContent } from '@fayz-ai/ui'
4
4
  import { Badge } from '@fayz-ai/ui'
5
5
  import { ScheduleEditor } from './ScheduleEditor'
6
+ import { AddressesTab } from './AddressesTab'
6
7
  import { useTranslation } from '@fayz-ai/core'
7
8
  import { usePluginRuntimeOptional as usePluginsOptional } from '@fayz-ai/core'
8
9
  import { getWidgetsForZone } from '@fayz-ai/core'
@@ -101,6 +102,10 @@ export function ScheduleTab({ item }: { item: any }) {
101
102
 
102
103
  export const PERSON_DETAIL_TABS = [
103
104
  { id: 'schedule', label: 'Schedule', icon: 'CalendarDays', component: ScheduleTab, visibleFor: ['staff'] },
105
+ // Core table (public.addresses), so every app with a person record gets this:
106
+ // delivery addresses for a client, pickup point for a supplier, home address
107
+ // for staff. Not shop-specific.
108
+ { id: 'addresses', label: 'Endereços', icon: 'MapPin', component: AddressesTab },
104
109
  { id: 'documents', label: 'Documents', icon: 'FileText', component: DocumentsTab },
105
110
  // Per-person financial statement — only shown when the financial plugin contributes
106
111
  // its widget (see requiresWidgetZone). Clients = paid invoices; staff = commissions.
@@ -59,6 +59,8 @@ export function createCrudPage<T extends { id: string }>(
59
59
  fields: entityDef.fields,
60
60
  source: 'app',
61
61
  archetype: entityDef.data?.archetype,
62
+ entityDef: entityDef as EntityDef,
63
+ mockData: options?.mockData as Array<{ id: string }> | undefined,
62
64
  })
63
65
 
64
66
  return GeneratedCrudPage
package/src/crud/index.ts CHANGED
@@ -10,3 +10,9 @@ export { ImportWizard } from './ImportWizard'
10
10
  export type { ImportRowError } from './ImportWizard'
11
11
  export { exportToCSV } from './csv-export'
12
12
  export { RelationSelect, RelationCell, useRelationOptions, loadRelationOptions, invalidateRelationOptions } from './relation-field'
13
+ // Link to another entity's detail page — used by detail tabs and by plugins
14
+ // that reference a record from a different module (e.g. the order list linking
15
+ // its customer).
16
+ // PersonLink is intentionally NOT re-exported: plugin-reports already exports
17
+ // a different component under that name and the collision breaks its build.
18
+ export { EntityLink, registerEntityPath, getPersonPath } from './archetypes/EntityLink'
package/src/env.d.ts CHANGED
@@ -8,6 +8,13 @@ interface ImportMetaEnv {
8
8
  /** Deployment-fixed canonical origin (e.g. https://beauty-saas.live.fayz.ai),
9
9
  * injected by the container. Used to build invite/magic-link redirects. */
10
10
  readonly VITE_APP_URL?: string
11
+ /** Fayz project this app belongs to, injected by the container. */
12
+ readonly VITE_FAYZ_PROJECT_ID?: string
13
+ /** Fayz API base (e.g. https://fayz.ai/api), injected by the container. */
14
+ readonly VITE_FAYZ_API_BASE_URL?: string
15
+ /** Publishable key for the project's web agent channel. Present only once an
16
+ * agent is enabled in Fayz — its absence is what leaves the assistant off. */
17
+ readonly VITE_FAYZ_AGENT_KEY?: string
11
18
  }
12
19
 
13
20
  interface ImportMeta {
package/src/index.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  // App factory (main entry point)
3
3
  // ---------------------------------------------------------------------------
4
4
  export { createFayzApp, AdminProviders } from './app/admin-app'
5
- export type { FayzAppConfig, AuthConfig, OrgConfig, ChatConfig, CustomPage, PageSection } from './app/config'
5
+ export type { FayzAppConfig, AuthConfig, OrgConfig, ChatConfig, FayzBillingConfig, CustomPage, PageSection } from './app/config'
6
6
 
7
7
  // Native admin scaffold — manifest-first entry. Importing this registers the
8
8
  // 'admin' scaffold so renderApp(manifest) can resolve it.
9
9
  export { defineSaas, AdminScaffold } from './app/scaffold'
10
+ export { deriveAgentContract } from './app/derive-contract'
11
+ export type { DerivedAgentSections } from './app/derive-contract'
10
12
  export { AdminShell } from './app/AdminShell'
11
13
  export { LoginPage } from './app/LoginPage'
12
14
  export { navigateTo as adminNavigateTo, useAdminPath } from './app/routing'
@@ -40,8 +42,10 @@ export { WidgetSlot } from './plugins/WidgetSlot'
40
42
  export { useFieldRules } from './hooks/useFieldRules'
41
43
 
42
44
  export { createCrudPage } from './crud/createCrudPage'
45
+ export { EntityLink, registerEntityPath, getPersonPath } from './crud/archetypes/EntityLink'
43
46
  // Client-orders archetype (used by beauty-saas client detail tab)
44
47
  export { ClientOrdersTab } from './shell/components/crud/archetypes/ClientOrdersTab'
48
+ export type { ClientOrdersProvider, ClientOrdersQuery, ClientOrdersNavigator, ClientDocument, ClientDocumentStage, ClientOrdersStageFilter } from './shell/types/client-orders'
45
49
  export { createClientOrdersProvider } from './shell/lib/create-client-orders-provider'
46
50
 
47
51
  // ---------------------------------------------------------------------------
@@ -90,15 +94,53 @@ export {
90
94
  usePermissionsStore,
91
95
  PermissionsProvider,
92
96
  usePermission,
97
+ usePermissionOptional,
93
98
  useHasPermission,
94
99
  usePermissions,
95
100
  } from './permissions/index'
96
101
  export type { PermissionsStore, PermissionsProviderProps } from './permissions/index'
97
102
 
103
+ // ---------------------------------------------------------------------------
104
+ // Access engine — role × plan decision, quantity limits, upgrade modal store
105
+ // ---------------------------------------------------------------------------
106
+ export {
107
+ AccessProvider,
108
+ useAccess,
109
+ useAccessOptional,
110
+ useLimit,
111
+ useLimitGuard,
112
+ useModuleNavAccess,
113
+ resolveAccess,
114
+ isEntitledByPlan,
115
+ invalidateLimit,
116
+ useUpgradeModalStore,
117
+ getAccessSnapshot,
118
+ checkAccess,
119
+ guardLimit,
120
+ agentDenial,
121
+ UPGRADE_URL,
122
+ } from './access/index'
123
+ export type {
124
+ AccessProviderProps,
125
+ AccessApi,
126
+ AccessDecision,
127
+ AccessSession,
128
+ DenyReason,
129
+ LimitState,
130
+ UpgradeModalPayload,
131
+ UpgradeModalStore,
132
+ AgentDenial,
133
+ AgentDenialLimit,
134
+ AgentGuardResult,
135
+ } from './access/index'
136
+
98
137
  // ---------------------------------------------------------------------------
99
138
  // Billing
100
139
  // ---------------------------------------------------------------------------
101
140
  export { useBillingStore } from './billing/index'
141
+ export { EntitlementGate, LimitGate } from './shell/components/billing/gates'
142
+ export { UpgradePrompt } from './shell/components/billing/UpgradePrompt'
143
+ export type { UpgradePromptProps } from './shell/components/billing/UpgradePrompt'
102
144
  export type { BillingStore, BillingState, Subscription, Invoice } from './billing/index'
103
145
 
104
146
  // ---------------------------------------------------------------------------
@@ -168,8 +210,14 @@ export { formatCurrency, type CurrencyConfig } from './lib/currency'
168
210
  export { PluginRegistryManager } from './plugins/PluginRegistryManager'
169
211
  export { useModuleNavigation } from './hooks/useModuleNavigation'
170
212
  export { PersonLink } from './components/shared/PersonLink'
213
+ export { ContactPicker } from './components/shared/ContactPicker'
214
+ export type { ContactPickerProps, ContactPickerValue } from './components/shared/ContactPicker'
215
+ export { createPerson } from './components/shared/create-person'
216
+ export type { CreatePersonInput, CreatedPerson } from './components/shared/create-person'
171
217
  export { resolveEntityHref } from './lib/entity-routes'
172
218
  export { usePluginPrefs } from './hooks/usePluginPrefs'
219
+ export { useTenantPluginSettings } from './shell/hooks/useTenantPluginSettings'
220
+ export type { TenantPluginSettings } from './shell/hooks/useTenantPluginSettings'
173
221
  export { setScheduleBlockConfig, getScheduleBlockConfig, subscribeScheduleBlockConfig } from './lib/schedule-config'
174
222
 
175
223
  // Assistant chat store — lets apps drive the shell's chat panel imperatively
package/src/org/store.ts CHANGED
@@ -62,7 +62,15 @@ export const useOrganizationStore = create<OrgStore>((set) => ({
62
62
  setLoading: (loading) => set({ loading }),
63
63
  reset: () => {
64
64
  setActiveTenantId(undefined)
65
- persistOrgId(null)
65
+ // NOTE: do NOT clear the persisted org id here. The org providers call
66
+ // reset() whenever the auth store transiently reports `user === null` —
67
+ // which happens on every fresh load while the Supabase session hydrates
68
+ // asynchronously. Wiping 'fayz:current-org' at that moment drops a
69
+ // multi-org user's workspace selection BEFORE loadOrgs can read it back,
70
+ // so the app always falls to memberships[0] on reload (single-org users
71
+ // never noticed). The pin is a last-selected hint that's re-validated
72
+ // against the fresh membership list on load, so it's safe to keep across
73
+ // sign-out too (a mismatching pin just falls back to memberships[0]).
66
74
  set({ currentOrg: null, userOrgs: [], members: [], loading: false })
67
75
  },
68
76
  }))
@@ -1,24 +1,29 @@
1
1
  import * as React from 'react'
2
2
  import type { FeatureDeclaration, PermissionsConfig, PermissionProfile } from '@fayz-ai/core'
3
+ import { usePluginRuntimeOptional } from '@fayz-ai/core'
3
4
  import { usePermissionsStore } from './store'
4
5
 
5
6
  // ---------------------------------------------------------------------------
6
7
  // Permission check helpers
7
8
  // ---------------------------------------------------------------------------
8
9
 
9
- function profileHasPermission(
10
- profile: PermissionProfile | null,
11
- feature: string,
12
- action?: string,
13
- ): boolean {
14
- if (!profile) return true
10
+ // The role-side check moved to @fayz-ai/core/access (single implementation for
11
+ // browser, headless and the Fayz broker). Re-exported to keep call-sites stable.
12
+ import { profileHasPermission } from '@fayz-ai/core/access'
13
+ export { profileHasPermission }
15
14
 
16
- const actions = profile.grants[feature]
17
- if (!actions) return false
18
-
19
- if (!action) return actions.length > 0
20
-
21
- return actions.includes(action) || actions.includes('manage')
15
+ /**
16
+ * Merge plugin-declared features with the app's own, deduped by id. The app's
17
+ * declaration wins on collision (it can override a plugin's label/actions).
18
+ */
19
+ function mergeFeatures(
20
+ pluginFeatures: FeatureDeclaration[] | undefined,
21
+ appFeatures: FeatureDeclaration[] | undefined,
22
+ ): FeatureDeclaration[] {
23
+ const byId = new Map<string, FeatureDeclaration>()
24
+ for (const f of pluginFeatures ?? []) byId.set(f.id, f)
25
+ for (const f of appFeatures ?? []) byId.set(f.id, f) // app override wins
26
+ return Array.from(byId.values())
22
27
  }
23
28
 
24
29
  // ---------------------------------------------------------------------------
@@ -47,11 +52,20 @@ export function PermissionsProvider({ config, children }: PermissionsProviderPro
47
52
  const { setFeatures, startImpersonation, stopImpersonation, currentProfile, isImpersonating } =
48
53
  usePermissionsStore()
49
54
 
55
+ // Plugins declare their own permission features via `declaredFeatures`, which the
56
+ // plugin runtime aggregates into `pluginFeatures`. Merging them here is what makes
57
+ // a plugin "born compatible" — its features show up in the RBAC matrix and gate
58
+ // checks without the app re-declaring them. The app's own `config.features` wins
59
+ // on id collisions (app override), so an app can refine a plugin's declaration.
60
+ const runtime = usePluginRuntimeOptional()
61
+ const pluginFeatures = runtime?.pluginFeatures
62
+
50
63
  React.useEffect(() => {
51
- if (config?.features) {
52
- setFeatures(config.features)
64
+ const merged = mergeFeatures(pluginFeatures, config?.features)
65
+ if (merged.length > 0) {
66
+ setFeatures(merged)
53
67
  }
54
- }, [config, setFeatures])
68
+ }, [config, pluginFeatures, setFeatures])
55
69
 
56
70
  function hasPermission(feature: string, action?: string): boolean {
57
71
  return profileHasPermission(currentProfile, feature, action)
@@ -1,8 +1,16 @@
1
1
  import React from 'react'
2
2
  import { useAuth } from '../../hooks/useAuth'
3
- import { usePermission } from '../../hooks/usePermission'
3
+ import { useTenantStore } from '../../stores/tenant.store'
4
4
  import type { MemberRole } from '../../types'
5
5
 
6
+ const ROLE_HIERARCHY: Record<MemberRole, number> = {
7
+ owner: 5,
8
+ admin: 4,
9
+ manager: 3,
10
+ staff: 2,
11
+ viewer: 1,
12
+ }
13
+
6
14
  interface ProtectedRouteProps {
7
15
  children?: React.ReactNode
8
16
  requiredRole?: MemberRole
@@ -16,7 +24,9 @@ function LoadingSkeleton() {
16
24
 
17
25
  export function ProtectedRoute({ children, requiredRole, fallback, onUnauthenticated }: ProtectedRouteProps) {
18
26
  const { user, loading, initialized } = useAuth()
19
- const { requireRole } = usePermission()
27
+ const currentRole = useTenantStore((s) => s.currentRole)
28
+ const requireRole = (role: MemberRole): boolean =>
29
+ currentRole ? ROLE_HIERARCHY[currentRole] >= ROLE_HIERARCHY[role] : false
20
30
 
21
31
  React.useEffect(() => {
22
32
  if (initialized && !loading && !user && onUnauthenticated) {
@@ -0,0 +1,81 @@
1
+ import * as React from 'react'
2
+ import { AlertTriangle, X } from 'lucide-react'
3
+ import { Button } from '@fayz-ai/ui'
4
+ import { useTranslation } from '../../hooks/useTranslation'
5
+ import { navigateTo } from '../../../app/routing'
6
+ import { useLimit, type LimitInfo } from './access-contract'
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // SoftLimitBanner — discreet, persistent notice at the top of the content when
10
+ // a monitored limit is EXCEEDED (used > max), not merely reached. This is the
11
+ // Notion-style soft-limit path: the public booking flow (storefront
12
+ // createCustomer) never blocks the end client, so a tenant can overshoot its cap
13
+ // — the admin sees this banner nudging an upgrade. Dismissable for the session.
14
+ //
15
+ // `keys` are the limit keys to monitor; the shell derives them from
16
+ // `manifest.billing.softLimitKeys` (documented seam). Each key mounts a probe so
17
+ // hooks stay unconditional even as the list is config-driven.
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function LimitProbe({ limitKey, onReport }: { limitKey: string; onReport: (key: string, info: LimitInfo) => void }) {
21
+ const info = useLimit(limitKey)
22
+ React.useEffect(() => {
23
+ onReport(limitKey, info)
24
+ }, [limitKey, info, onReport])
25
+ return null
26
+ }
27
+
28
+ export function SoftLimitBanner({ keys }: { keys: string[] }) {
29
+ const { t } = useTranslation()
30
+ const tr = (key: string, fallback: string, params?: Record<string, string | number>) => {
31
+ let v = t(key)
32
+ if (!v || v === key) v = fallback
33
+ if (params) for (const [k, val] of Object.entries(params)) v = v.replace(`{${k}}`, String(val)).replace(`{{${k}}}`, String(val))
34
+ return v
35
+ }
36
+
37
+ const [over, setOver] = React.useState<Record<string, boolean>>({})
38
+ const [dismissed, setDismissed] = React.useState<Record<string, boolean>>({})
39
+
40
+ const report = React.useCallback((key: string, info: LimitInfo) => {
41
+ const isOver = !info.unlimited && !info.loading && info.used > info.max
42
+ setOver((prev) => {
43
+ if (isOver === Boolean(prev[key])) return prev
44
+ return { ...prev, [key]: isOver }
45
+ })
46
+ }, [])
47
+
48
+ // Stable key list (avoid remounting probes each render).
49
+ const monitored = React.useMemo(() => Array.from(new Set(keys)), [keys])
50
+
51
+ const activeKey = monitored.find((k) => over[k] && !dismissed[k])
52
+
53
+ return (
54
+ <>
55
+ {monitored.map((k) => (
56
+ <LimitProbe key={k} limitKey={k} onReport={report} />
57
+ ))}
58
+ {activeKey && (
59
+ <div className="mb-3 flex flex-wrap items-center gap-3 rounded-lg border border-amber-400/50 bg-amber-50/70 px-4 py-2.5 text-sm dark:bg-amber-950/25">
60
+ <AlertTriangle className="h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
61
+ <span className="min-w-0 flex-1 text-foreground">
62
+ {tr('banner.overLimit', "You've exceeded the {label} limit of your plan — upgrade to keep growing.", {
63
+ label: tr(`limit.label.${activeKey}`, activeKey),
64
+ })}
65
+ </span>
66
+ <Button size="sm" onClick={() => navigateTo('/settings/subscription')}>
67
+ {tr('upgrade.cta', 'View plans')}
68
+ </Button>
69
+ <button
70
+ type="button"
71
+ aria-label={tr('common.close', 'Close')}
72
+ className="rounded p-1 text-muted-foreground transition-colors hover:bg-amber-400/20 hover:text-foreground"
73
+ onClick={() => setDismissed((prev) => ({ ...prev, [activeKey]: true }))}
74
+ >
75
+ <X className="h-4 w-4" />
76
+ </button>
77
+ </div>
78
+ )}
79
+ </>
80
+ )
81
+ }