@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import type { PermissionProfile, Plan } from '@fayz-ai/core'
|
|
3
|
+
import { resolveAccess, isEntitledByPlan, resolveLimit } from './resolver'
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Fixtures
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const ownerProfile: PermissionProfile = { id: 'owner', name: 'Owner', grants: {} }
|
|
10
|
+
|
|
11
|
+
const staffProfile: PermissionProfile = {
|
|
12
|
+
id: 'staff',
|
|
13
|
+
name: 'Staff',
|
|
14
|
+
grants: { clients: ['read'], reports: ['read', 'edit'] },
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function plan(entitlements: Plan['entitlements']): Plan {
|
|
18
|
+
return {
|
|
19
|
+
id: 'p',
|
|
20
|
+
name: 'Plan',
|
|
21
|
+
price: 0,
|
|
22
|
+
currency: 'USD',
|
|
23
|
+
interval: 'month',
|
|
24
|
+
features: [],
|
|
25
|
+
entitlements,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// resolveAccess — role × plan composition
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
describe('resolveAccess', () => {
|
|
34
|
+
it('denies by ROLE when the profile lacks the grant (non-owner)', () => {
|
|
35
|
+
const d = resolveAccess({ profile: staffProfile, plan: null }, 'marketing')
|
|
36
|
+
expect(d).toEqual({ allowed: false, reason: 'role' })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('role denial takes precedence over plan denial', () => {
|
|
40
|
+
// staff lacks 'marketing' (role) AND the plan gates it off (plan). Role wins.
|
|
41
|
+
const p = plan({ features: { marketing: false } })
|
|
42
|
+
const d = resolveAccess({ profile: staffProfile, plan: p }, 'marketing')
|
|
43
|
+
expect(d.reason).toBe('role')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('allows when role grants and the plan does not gate the feature', () => {
|
|
47
|
+
const d = resolveAccess({ profile: staffProfile, plan: plan({}) }, 'clients')
|
|
48
|
+
expect(d).toEqual({ allowed: true })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('denies by PLAN when role grants but the plan gates the feature off', () => {
|
|
52
|
+
const p = plan({ features: { reports: false } })
|
|
53
|
+
const d = resolveAccess({ profile: staffProfile, plan: p }, 'reports')
|
|
54
|
+
expect(d).toEqual({ allowed: false, reason: 'plan' })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('OWNER passes the role check but does NOT bypass the plan', () => {
|
|
58
|
+
const p = plan({ features: { reports: false } })
|
|
59
|
+
const d = resolveAccess({ profile: ownerProfile, plan: p }, 'reports')
|
|
60
|
+
expect(d).toEqual({ allowed: false, reason: 'plan' })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('owner is allowed when the plan does not gate the feature', () => {
|
|
64
|
+
const d = resolveAccess({ profile: ownerProfile, plan: plan({}) }, 'anything')
|
|
65
|
+
expect(d).toEqual({ allowed: true })
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('a feature absent from entitlements is allowed (additive plans)', () => {
|
|
69
|
+
const p = plan({ features: { other: false } })
|
|
70
|
+
const d = resolveAccess({ profile: ownerProfile, plan: p }, 'prontuario')
|
|
71
|
+
expect(d).toEqual({ allowed: true })
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('respects the action argument (manage/absent)', () => {
|
|
75
|
+
// staff has clients:['read'] only → write denied by role.
|
|
76
|
+
expect(resolveAccess({ profile: staffProfile, plan: null }, 'clients', 'write').reason).toBe('role')
|
|
77
|
+
expect(resolveAccess({ profile: staffProfile, plan: null }, 'clients', 'read').allowed).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('impersonation preview composes: previewing a restricted role loses owner bypass', () => {
|
|
81
|
+
// During impersonation the session.profile IS the previewed (staff) role.
|
|
82
|
+
const d = resolveAccess({ profile: staffProfile, plan: plan({}) }, 'billing')
|
|
83
|
+
expect(d).toEqual({ allowed: false, reason: 'role' })
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// isEntitledByPlan
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
describe('isEntitledByPlan', () => {
|
|
92
|
+
it('null plan is entitled (no gating)', () => {
|
|
93
|
+
expect(isEntitledByPlan(null, 'reports')).toBe(true)
|
|
94
|
+
})
|
|
95
|
+
it('explicit false denies', () => {
|
|
96
|
+
expect(isEntitledByPlan(plan({ features: { reports: false } }), 'reports')).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
it('explicit true and absent both allow', () => {
|
|
99
|
+
expect(isEntitledByPlan(plan({ features: { reports: true } }), 'reports')).toBe(true)
|
|
100
|
+
expect(isEntitledByPlan(plan({}), 'reports')).toBe(true)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// resolveLimit — quantity math
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
describe('resolveLimit', () => {
|
|
109
|
+
it('unlimited when there is no declaration', () => {
|
|
110
|
+
const s = resolveLimit({ cap: 100, hasDeclaration: false, used: 50 })
|
|
111
|
+
expect(s.unlimited).toBe(true)
|
|
112
|
+
expect(s.max).toBe(Infinity)
|
|
113
|
+
expect(s.atLimit).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('unlimited when the plan has no cap for the key', () => {
|
|
117
|
+
const s = resolveLimit({ cap: undefined, hasDeclaration: true, used: 999 })
|
|
118
|
+
expect(s.unlimited).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('-1 means unlimited', () => {
|
|
122
|
+
const s = resolveLimit({ cap: -1, hasDeclaration: true, used: 999 })
|
|
123
|
+
expect(s.unlimited).toBe(true)
|
|
124
|
+
expect(s.remaining).toBe(Infinity)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('computes remaining and not-at-limit below the cap', () => {
|
|
128
|
+
const s = resolveLimit({ cap: 100, hasDeclaration: true, used: 40 })
|
|
129
|
+
expect(s).toMatchObject({ unlimited: false, max: 100, remaining: 60, atLimit: false })
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('atLimit when used reaches the cap', () => {
|
|
133
|
+
const s = resolveLimit({ cap: 100, hasDeclaration: true, used: 100 })
|
|
134
|
+
expect(s.atLimit).toBe(true)
|
|
135
|
+
expect(s.remaining).toBe(0)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('clamps remaining at 0 when over the cap', () => {
|
|
139
|
+
const s = resolveLimit({ cap: 2, hasDeclaration: true, used: 5 })
|
|
140
|
+
expect(s.remaining).toBe(0)
|
|
141
|
+
expect(s.atLimit).toBe(true)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The decision engine moved to @fayz-ai/core/access so the SAME implementation
|
|
2
|
+
// runs in the browser, in headless client-plane code and in the Fayz broker
|
|
3
|
+
// (server-side agent authorization). Re-exported here so existing call-sites
|
|
4
|
+
// keep importing from saas unchanged.
|
|
5
|
+
export { isEntitledByPlan, resolveAccess, resolveLimit } from '@fayz-ai/core/access'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AuthUser, Organization, PermissionProfile, Plan, PermissionAction } from '@fayz-ai/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decision types come from the shared engine in @fayz-ai/core/access.
|
|
5
|
+
* - `role` — the user's RBAC profile lacks the permission (fix: grant the role).
|
|
6
|
+
* - `plan` — the tenant's plan does not entitle the feature (fix: upgrade).
|
|
7
|
+
* - `limit` — a guarded write would exceed a plan cap (fix: upgrade).
|
|
8
|
+
* The distinction drives the UI: `role` → AccessDenied, `plan`/`limit` →
|
|
9
|
+
* UpgradePrompt/UpgradeModal.
|
|
10
|
+
*/
|
|
11
|
+
import type { AccessDecision } from '@fayz-ai/core/access'
|
|
12
|
+
|
|
13
|
+
export type { DenyReason, AccessDecision } from '@fayz-ai/core/access'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The reactive snapshot the access engine resolves against. Assembled from the
|
|
17
|
+
* singleton stores (auth user, current org incl. plan id, current permission
|
|
18
|
+
* profile, billing plans). This is intentionally ONE extensible object: future
|
|
19
|
+
* session parameters (active location, feature-flag cohort, delegated scope, …)
|
|
20
|
+
* enter here so the resolver signature never churns.
|
|
21
|
+
*/
|
|
22
|
+
export interface AccessSession {
|
|
23
|
+
user: AuthUser | null
|
|
24
|
+
org: Organization | null
|
|
25
|
+
/** Effective profile — becomes the previewed role during impersonation. */
|
|
26
|
+
profile: PermissionProfile | null
|
|
27
|
+
/** The org's currently-active plan (resolved from billing plans by org.plan). */
|
|
28
|
+
plan: Plan | null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LimitState {
|
|
32
|
+
key: string
|
|
33
|
+
/** Cap from the plan (`-1`/unlimited surfaces as `Infinity` here). */
|
|
34
|
+
max: number
|
|
35
|
+
used: number
|
|
36
|
+
remaining: number
|
|
37
|
+
atLimit: boolean
|
|
38
|
+
unlimited: boolean
|
|
39
|
+
loading: boolean
|
|
40
|
+
/** Re-count (bypasses the short-lived cache). */
|
|
41
|
+
refresh(): void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AccessApi {
|
|
45
|
+
can(feature: string, action?: PermissionAction): AccessDecision
|
|
46
|
+
entitled(feature: string): boolean
|
|
47
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Payload describing WHY the upgrade modal opened, so the visual component (owned
|
|
5
|
+
* by the shell/gates layer) can tailor its copy — a hit quantity cap
|
|
6
|
+
* (`limitKey`) or a plan-gated feature (`feature`).
|
|
7
|
+
*/
|
|
8
|
+
export interface UpgradeModalPayload {
|
|
9
|
+
limitKey?: string
|
|
10
|
+
feature?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UpgradeModalStore {
|
|
14
|
+
/** The active payload, or null when closed. */
|
|
15
|
+
current: UpgradeModalPayload | null
|
|
16
|
+
open(payload: UpgradeModalPayload): void
|
|
17
|
+
close(): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Global, lightweight store for the single app-wide UpgradeModal. `useLimitGuard`
|
|
22
|
+
* and imperative create handlers call `open(...)` when a limit blocks; the visual
|
|
23
|
+
* modal (rendered once near the shell root by the gates layer) subscribes to
|
|
24
|
+
* `current`. Kept as a zustand singleton so store + component share one instance.
|
|
25
|
+
*/
|
|
26
|
+
export const useUpgradeModalStore = create<UpgradeModalStore>((set) => ({
|
|
27
|
+
current: null,
|
|
28
|
+
open: (payload) => set({ current: payload }),
|
|
29
|
+
close: () => set({ current: null }),
|
|
30
|
+
}))
|