@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.
- package/dist/access/context.d.ts +47 -0
- package/dist/access/context.d.ts.map +1 -0
- package/dist/access/headless.d.ts +21 -0
- package/dist/access/headless.d.ts.map +1 -0
- package/dist/access/index.d.ts +12 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/limits-registry.d.ts +33 -0
- package/dist/access/limits-registry.d.ts.map +1 -0
- package/dist/access/module-nav.d.ts +15 -0
- package/dist/access/module-nav.d.ts.map +1 -0
- package/dist/access/resolver.d.ts +2 -0
- package/dist/access/resolver.d.ts.map +1 -0
- package/dist/access/types.d.ts +43 -0
- package/dist/access/types.d.ts.map +1 -0
- package/dist/access/upgrade-modal-store.d.ts +23 -0
- package/dist/access/upgrade-modal-store.d.ts.map +1 -0
- package/dist/app/AdminShell.d.ts +3 -0
- package/dist/app/AdminShell.d.ts.map +1 -1
- package/dist/app/admin-app.d.ts +3 -1
- package/dist/app/admin-app.d.ts.map +1 -1
- package/dist/app/config.d.ts +47 -2
- package/dist/app/config.d.ts.map +1 -1
- package/dist/app/core-entities.d.ts +25 -0
- package/dist/app/core-entities.d.ts.map +1 -0
- package/dist/app/derive-contract.d.ts +8 -0
- package/dist/app/derive-contract.d.ts.map +1 -0
- package/dist/app/scaffold.d.ts.map +1 -1
- package/dist/archetype-lookup.d.ts.map +1 -1
- package/dist/billing/index.cjs +6 -2
- package/dist/billing/index.d.ts +2 -0
- package/dist/billing/index.d.ts.map +1 -1
- package/dist/billing/index.js +1 -1
- package/dist/billing/plan-badge.d.ts +17 -0
- package/dist/billing/plan-badge.d.ts.map +1 -0
- package/dist/billing/store.d.ts +6 -1
- package/dist/billing/store.d.ts.map +1 -1
- package/dist/{chunk-3M25GRRU.js → chunk-I3DY47N5.js} +3 -4
- package/dist/chunk-I3DY47N5.js.map +1 -0
- package/dist/chunk-NNFGMUF2.cjs +39 -0
- package/dist/chunk-NNFGMUF2.cjs.map +1 -0
- package/dist/{chunk-4XTQ4V5L.cjs → chunk-SAZLW54Y.cjs} +16 -12
- package/dist/chunk-SAZLW54Y.cjs.map +1 -0
- package/dist/{chunk-IPWCHJPE.cjs → chunk-VKYZACG5.cjs} +5 -6
- package/dist/chunk-VKYZACG5.cjs.map +1 -0
- package/dist/chunk-VN5NTK26.js +36 -0
- package/dist/chunk-VN5NTK26.js.map +1 -0
- package/dist/{chunk-XNWCGJYH.js → chunk-VTEP2AQX.js} +15 -11
- package/dist/chunk-VTEP2AQX.js.map +1 -0
- package/dist/components/shared/ContactPicker.d.ts +92 -0
- package/dist/components/shared/ContactPicker.d.ts.map +1 -0
- package/dist/components/shared/PersonLink.d.ts +12 -1
- package/dist/components/shared/PersonLink.d.ts.map +1 -1
- package/dist/components/shared/create-person.d.ts +20 -0
- package/dist/components/shared/create-person.d.ts.map +1 -0
- package/dist/crud/CrudDetailPage.d.ts.map +1 -1
- package/dist/crud/CrudFormPage.d.ts.map +1 -1
- package/dist/crud/CrudListView.d.ts.map +1 -1
- package/dist/crud/CrudPage.d.ts.map +1 -1
- package/dist/crud/archetypes/AddressesTab.d.ts +21 -0
- package/dist/crud/archetypes/AddressesTab.d.ts.map +1 -0
- package/dist/crud/archetypes/PersonDetailTabs.d.ts +2 -1
- package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -1
- package/dist/crud/createCrudPage.d.ts.map +1 -1
- package/dist/crud/index.d.ts +1 -0
- package/dist/crud/index.d.ts.map +1 -1
- package/dist/index.cjs +4281 -716
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4067 -542
- package/dist/index.js.map +1 -1
- package/dist/org/index.cjs +11 -11
- package/dist/org/index.js +2 -2
- package/dist/org/store.d.ts.map +1 -1
- package/dist/permissions/context.d.ts +2 -0
- package/dist/permissions/context.d.ts.map +1 -1
- package/dist/permissions/index.cjs +7 -7
- package/dist/permissions/index.js +1 -1
- package/dist/shell/components/auth/ProtectedRoute.d.ts.map +1 -1
- package/dist/shell/components/billing/SoftLimitBanner.d.ts +5 -0
- package/dist/shell/components/billing/SoftLimitBanner.d.ts.map +1 -0
- package/dist/shell/components/billing/SubscriptionPage.d.ts +5 -0
- package/dist/shell/components/billing/SubscriptionPage.d.ts.map +1 -0
- package/dist/shell/components/billing/UpgradeModal.d.ts +3 -0
- package/dist/shell/components/billing/UpgradeModal.d.ts.map +1 -0
- package/dist/shell/components/billing/UpgradePrompt.d.ts +10 -0
- package/dist/shell/components/billing/UpgradePrompt.d.ts.map +1 -0
- package/dist/shell/components/billing/access-contract.d.ts +18 -0
- package/dist/shell/components/billing/access-contract.d.ts.map +1 -0
- package/dist/shell/components/billing/gates.d.ts +14 -0
- package/dist/shell/components/billing/gates.d.ts.map +1 -0
- package/dist/shell/components/billing/index.d.ts +9 -2
- package/dist/shell/components/billing/index.d.ts.map +1 -1
- package/dist/shell/components/chat/ChatFab.d.ts +3 -1
- package/dist/shell/components/chat/ChatFab.d.ts.map +1 -1
- package/dist/shell/components/chat/ChatPanel.d.ts +3 -1
- package/dist/shell/components/chat/ChatPanel.d.ts.map +1 -1
- package/dist/shell/components/chat/ConfirmActionCard.d.ts +13 -0
- package/dist/shell/components/chat/ConfirmActionCard.d.ts.map +1 -0
- package/dist/shell/components/chat/markdown.d.ts +5 -0
- package/dist/shell/components/chat/markdown.d.ts.map +1 -0
- package/dist/shell/components/organization/TeamTab.d.ts.map +1 -1
- package/dist/shell/components/organization/TenantOnboarding.d.ts.map +1 -1
- package/dist/shell/components/organization/index.d.ts +0 -1
- package/dist/shell/components/organization/index.d.ts.map +1 -1
- package/dist/shell/components/settings/ConnectedCompanySettings.d.ts.map +1 -1
- package/dist/shell/components/settings/ConnectedFieldRulesSettings.d.ts.map +1 -1
- package/dist/shell/components/settings/ConnectedSecuritySettings.d.ts.map +1 -1
- package/dist/shell/components/settings/LocationsCrudPage.d.ts +1 -1
- package/dist/shell/components/settings/LocationsCrudPage.d.ts.map +1 -1
- package/dist/shell/components/settings/SettingsPage.d.ts +23 -2
- package/dist/shell/components/settings/SettingsPage.d.ts.map +1 -1
- package/dist/shell/hooks/index.d.ts +3 -2
- package/dist/shell/hooks/index.d.ts.map +1 -1
- package/dist/shell/hooks/useAITools.d.ts +2 -0
- package/dist/shell/hooks/useAITools.d.ts.map +1 -1
- package/dist/shell/hooks/useChat.d.ts +22 -0
- package/dist/shell/hooks/useChat.d.ts.map +1 -1
- package/dist/shell/hooks/useSystemPermission.d.ts +3 -0
- package/dist/shell/hooks/useSystemPermission.d.ts.map +1 -0
- package/dist/shell/hooks/useTenantPluginSettings.d.ts +12 -0
- package/dist/shell/hooks/useTenantPluginSettings.d.ts.map +1 -0
- package/dist/shell/lib/ai-tool-handlers.d.ts +119 -0
- package/dist/shell/lib/ai-tool-handlers.d.ts.map +1 -0
- package/dist/shell/lib/core-ai-tools.d.ts +14 -0
- package/dist/shell/lib/core-ai-tools.d.ts.map +1 -1
- package/dist/shell/lib/create-client-orders-provider.d.ts.map +1 -1
- package/dist/shell/lib/fayz-agent.d.ts +23 -0
- package/dist/shell/lib/fayz-agent.d.ts.map +1 -0
- package/dist/shell/lib/i18n.d.ts.map +1 -1
- package/dist/shell/stores/chat.store.d.ts +69 -0
- package/dist/shell/stores/chat.store.d.ts.map +1 -1
- package/dist/shell/types/billing.d.ts +7 -0
- package/dist/shell/types/billing.d.ts.map +1 -1
- package/dist/shell/types/plugins.d.ts +2 -45
- package/dist/shell/types/plugins.d.ts.map +1 -1
- package/dist/stores/createCrudStore.d.ts.map +1 -1
- package/package.json +14 -11
- package/src/access/context.tsx +228 -0
- package/src/access/headless.ts +71 -0
- package/src/access/index.ts +27 -0
- package/src/access/limits-registry.ts +86 -0
- package/src/access/module-nav.test.ts +78 -0
- package/src/access/module-nav.ts +82 -0
- package/src/access/resolver.test.ts +143 -0
- package/src/access/resolver.ts +5 -0
- package/src/access/types.ts +47 -0
- package/src/access/upgrade-modal-store.ts +30 -0
- package/src/app/AdminShell.tsx +451 -68
- package/src/app/admin-app.tsx +46 -9
- package/src/app/config.ts +54 -1
- package/src/app/core-entities.ts +110 -0
- package/src/app/derive-contract.test.ts +155 -0
- package/src/app/derive-contract.ts +214 -0
- package/src/app/scaffold.tsx +4 -0
- package/src/archetype-lookup.ts +8 -2
- package/src/billing/index.ts +2 -0
- package/src/billing/plan-badge.ts +32 -0
- package/src/billing/store.ts +8 -1
- package/src/components/shared/ContactPicker.tsx +394 -0
- package/src/components/shared/PersonLink.tsx +11 -3
- package/src/components/shared/create-person.ts +74 -0
- package/src/crud/CrudDetailPage.tsx +5 -1
- package/src/crud/CrudFormPage.tsx +113 -6
- package/src/crud/CrudListView.tsx +16 -1
- package/src/crud/CrudPage.tsx +27 -5
- package/src/crud/DeleteConfirmDialog.tsx +1 -1
- package/src/crud/archetypes/AddressesTab.tsx +387 -0
- package/src/crud/archetypes/PersonDetailTabs.tsx +5 -0
- package/src/crud/createCrudPage.tsx +2 -0
- package/src/crud/index.ts +6 -0
- package/src/env.d.ts +7 -0
- package/src/index.ts +49 -1
- package/src/org/store.ts +9 -1
- package/src/permissions/context.tsx +29 -15
- package/src/shell/components/auth/ProtectedRoute.tsx +12 -2
- package/src/shell/components/billing/SoftLimitBanner.tsx +81 -0
- package/src/shell/components/billing/SubscriptionPage.tsx +372 -0
- package/src/shell/components/billing/UpgradeModal.tsx +102 -0
- package/src/shell/components/billing/UpgradePrompt.tsx +229 -0
- package/src/shell/components/billing/access-contract.ts +64 -0
- package/src/shell/components/billing/gates.tsx +62 -0
- package/src/shell/components/billing/index.ts +18 -2
- package/src/shell/components/chat/ChatFab.tsx +4 -2
- package/src/shell/components/chat/ChatPanel.tsx +174 -23
- package/src/shell/components/chat/ConfirmActionCard.tsx +57 -0
- package/src/shell/components/chat/markdown.tsx +102 -0
- package/src/shell/components/organization/TeamTab.tsx +21 -4
- package/src/shell/components/organization/TenantOnboarding.tsx +3 -1
- package/src/shell/components/organization/index.ts +0 -1
- package/src/shell/components/settings/ConnectedCompanySettings.tsx +4 -1
- package/src/shell/components/settings/ConnectedFieldRulesSettings.tsx +3 -1
- package/src/shell/components/settings/ConnectedSecuritySettings.tsx +3 -1
- package/src/shell/components/settings/LocationsCrudPage.ts +6 -1
- package/src/shell/components/settings/SettingsPage.tsx +63 -21
- package/src/shell/hooks/index.ts +3 -2
- package/src/shell/hooks/useAITools.ts +27 -23
- package/src/shell/hooks/useChat.ts +381 -16
- package/src/shell/hooks/useSystemPermission.ts +23 -0
- package/src/shell/hooks/useTenantPluginSettings.ts +127 -0
- package/src/shell/lib/ai-tool-handlers.ts +910 -0
- package/src/shell/lib/core-ai-tools.ts +234 -2
- package/src/shell/lib/create-client-orders-provider.ts +24 -2
- package/src/shell/lib/fayz-agent.ts +56 -0
- package/src/shell/lib/i18n.ts +71 -0
- package/src/shell/stores/chat.store.ts +118 -1
- package/src/shell/styles.css +30 -0
- package/src/shell/types/billing.ts +8 -0
- package/src/shell/types/plugins.ts +13 -50
- package/src/stores/createCrudStore.ts +9 -0
- package/dist/chunk-3M25GRRU.js.map +0 -1
- package/dist/chunk-4XTQ4V5L.cjs.map +0 -1
- package/dist/chunk-5GYQK5IW.js +0 -21
- package/dist/chunk-5GYQK5IW.js.map +0 -1
- package/dist/chunk-IPWCHJPE.cjs.map +0 -1
- package/dist/chunk-KYID2MR6.cjs +0 -23
- package/dist/chunk-KYID2MR6.cjs.map +0 -1
- package/dist/chunk-XNWCGJYH.js.map +0 -1
- package/dist/shell/components/billing/BillingPage.d.ts +0 -7
- package/dist/shell/components/billing/BillingPage.d.ts.map +0 -1
- package/dist/shell/components/billing/PaywallGate.d.ts +0 -11
- package/dist/shell/components/billing/PaywallGate.d.ts.map +0 -1
- package/dist/shell/components/organization/PermissionGate.d.ts +0 -11
- package/dist/shell/components/organization/PermissionGate.d.ts.map +0 -1
- package/dist/shell/hooks/useBilling.d.ts +0 -14
- package/dist/shell/hooks/useBilling.d.ts.map +0 -1
- package/dist/shell/hooks/usePermission.d.ts +0 -16
- package/dist/shell/hooks/usePermission.d.ts.map +0 -1
- package/src/shell/components/billing/BillingPage.tsx +0 -56
- package/src/shell/components/billing/PaywallGate.tsx +0 -80
- package/src/shell/components/organization/PermissionGate.tsx +0 -16
- package/src/shell/hooks/useBilling.ts +0 -151
- 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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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 {
|
|
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
|
|
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
|
+
}
|