@fayz-ai/saas 0.8.0 → 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 +4300 -722
- 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 +4080 -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/crud.d.ts +1 -1
- package/dist/shell/types/crud.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 +15 -12
- 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 +8 -1
- package/src/crud/CrudFormPage.tsx +124 -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/crud.ts +1 -1
- 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
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import { ArrowUp, Settings2 } from 'lucide-react'
|
|
2
|
+
import { ArrowUp, ArrowUpRight, ChevronDown, History, Loader2, SquarePen, Settings2, Wrench } from 'lucide-react'
|
|
3
3
|
import { cn } from '../../lib/cn'
|
|
4
4
|
import { useTranslation } from '../../hooks/useTranslation'
|
|
5
|
-
import { useChatStore, type ChatMessage } from '../../stores/chat.store'
|
|
5
|
+
import { useChatStore, type ChatMessage, type ChatToolCall, type ChatRecordLink } from '../../stores/chat.store'
|
|
6
6
|
import { useChat } from '../../hooks/useChat'
|
|
7
7
|
import { useAITools } from '../../hooks/useAITools'
|
|
8
8
|
import { ChatSuggestions, ChatToolsPanel } from './ChatSuggestions'
|
|
9
|
+
import { ConfirmActionCard } from './ConfirmActionCard'
|
|
10
|
+
import { ChatMarkdown } from './markdown'
|
|
11
|
+
import type { FayzAgentConnectionConfig } from '../../lib/fayz-agent'
|
|
12
|
+
// NOTE: src/lib copy — the one AdminShell's setEntityRouteMap fills. The
|
|
13
|
+
// shell/lib duplicate is a legacy shim nobody populates ('two of everything').
|
|
14
|
+
import { resolveEntityRoute, resolveEntityHref } from '../../../lib/entity-routes'
|
|
15
|
+
import { useRouter } from '../../lib/router'
|
|
9
16
|
|
|
10
17
|
interface ChatPanelProps {
|
|
11
18
|
title?: string
|
|
12
19
|
apiEndpoint?: string
|
|
13
20
|
systemPrompt?: string
|
|
21
|
+
agent?: FayzAgentConnectionConfig | false
|
|
14
22
|
className?: string
|
|
15
23
|
}
|
|
16
24
|
|
|
@@ -18,14 +26,19 @@ export function ChatPanel({
|
|
|
18
26
|
title = 'Assistant',
|
|
19
27
|
apiEndpoint,
|
|
20
28
|
systemPrompt,
|
|
29
|
+
agent,
|
|
21
30
|
className,
|
|
22
31
|
}: ChatPanelProps) {
|
|
23
32
|
const { isOpen, messages, isStreaming } = useChatStore()
|
|
24
|
-
const {
|
|
33
|
+
const {
|
|
34
|
+
sendMessage, isConfigured, pendingAction, resolvePendingAction,
|
|
35
|
+
activeTools, conversations, loadConversations, resumeConversation, startNewConversation,
|
|
36
|
+
} = useChat({ apiEndpoint, systemPrompt, agent })
|
|
25
37
|
const { t } = useTranslation()
|
|
26
38
|
const { suggestions, toolGroups } = useAITools()
|
|
27
39
|
const [input, setInput] = React.useState('')
|
|
28
40
|
const [showTools, setShowTools] = React.useState(false)
|
|
41
|
+
const [showHistory, setShowHistory] = React.useState(false)
|
|
29
42
|
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
30
43
|
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
31
44
|
|
|
@@ -38,18 +51,20 @@ export function ChatPanel({
|
|
|
38
51
|
|
|
39
52
|
React.useEffect(() => {
|
|
40
53
|
if (isOpen) {
|
|
54
|
+
loadConversations()
|
|
41
55
|
const timer = setTimeout(() => inputRef.current?.focus(), 100)
|
|
42
56
|
return () => clearTimeout(timer)
|
|
43
57
|
}
|
|
44
58
|
setShowTools(false)
|
|
45
|
-
|
|
59
|
+
setShowHistory(false)
|
|
60
|
+
}, [isOpen, loadConversations])
|
|
46
61
|
|
|
47
62
|
if (!isOpen) return null
|
|
48
63
|
|
|
49
64
|
const handleSubmit = (e: React.FormEvent) => {
|
|
50
65
|
e.preventDefault()
|
|
51
66
|
const text = input.trim()
|
|
52
|
-
if (!text || isStreaming) return
|
|
67
|
+
if (!text || isStreaming || !isConfigured) return
|
|
53
68
|
setInput('')
|
|
54
69
|
setShowTools(false)
|
|
55
70
|
sendMessage(text)
|
|
@@ -73,9 +88,31 @@ export function ChatPanel({
|
|
|
73
88
|
<div className="h-1.5 w-1.5 rounded-full bg-success" />
|
|
74
89
|
<span className="text-xs font-semibold text-foreground">{title}</span>
|
|
75
90
|
</div>
|
|
91
|
+
<div className="flex items-center gap-0.5">
|
|
92
|
+
{hasMessages && (
|
|
93
|
+
<button
|
|
94
|
+
onClick={startNewConversation}
|
|
95
|
+
className="inline-flex items-center rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground"
|
|
96
|
+
title={t('chat.newConversation')}
|
|
97
|
+
>
|
|
98
|
+
<SquarePen className="h-3 w-3" />
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
{conversations.length > 0 && (
|
|
102
|
+
<button
|
|
103
|
+
onClick={() => { setShowHistory(!showHistory); setShowTools(false) }}
|
|
104
|
+
className={cn(
|
|
105
|
+
'inline-flex items-center rounded-md p-1.5 transition-colors',
|
|
106
|
+
showHistory ? 'bg-muted text-foreground' : 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
|
|
107
|
+
)}
|
|
108
|
+
title={t('chat.history')}
|
|
109
|
+
>
|
|
110
|
+
<History className="h-3 w-3" />
|
|
111
|
+
</button>
|
|
112
|
+
)}
|
|
76
113
|
{totalTools > 0 && (
|
|
77
114
|
<button
|
|
78
|
-
onClick={() => setShowTools(!showTools)}
|
|
115
|
+
onClick={() => { setShowTools(!showTools); setShowHistory(false) }}
|
|
79
116
|
className={cn(
|
|
80
117
|
'inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-[10px] transition-colors',
|
|
81
118
|
showTools
|
|
@@ -88,10 +125,31 @@ export function ChatPanel({
|
|
|
88
125
|
<span className="font-medium">{totalTools}</span>
|
|
89
126
|
</button>
|
|
90
127
|
)}
|
|
128
|
+
</div>
|
|
91
129
|
</div>
|
|
92
130
|
|
|
93
|
-
{/*
|
|
94
|
-
{
|
|
131
|
+
{/* History drawer */}
|
|
132
|
+
{showHistory ? (
|
|
133
|
+
<div className="min-h-0 flex-1 overflow-y-auto py-1">
|
|
134
|
+
{conversations.map((c) => (
|
|
135
|
+
<button
|
|
136
|
+
key={c.id}
|
|
137
|
+
onClick={() => {
|
|
138
|
+
setShowHistory(false)
|
|
139
|
+
resumeConversation(c.id)
|
|
140
|
+
}}
|
|
141
|
+
className="flex w-full flex-col items-start gap-0.5 px-4 py-2 text-left transition-colors hover:bg-muted/50"
|
|
142
|
+
>
|
|
143
|
+
<span className="line-clamp-1 text-[13px] text-foreground">
|
|
144
|
+
{c.title ?? t('chat.newConversation')}
|
|
145
|
+
</span>
|
|
146
|
+
<span className="text-[10px] text-muted-foreground">
|
|
147
|
+
{new Date(c.updatedAt).toLocaleString()}
|
|
148
|
+
</span>
|
|
149
|
+
</button>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
) : showTools ? (
|
|
95
153
|
<ChatToolsPanel toolGroups={toolGroups} onClose={() => setShowTools(false)} />
|
|
96
154
|
) : (
|
|
97
155
|
/* Messages / suggestions area */
|
|
@@ -103,18 +161,41 @@ export function ChatPanel({
|
|
|
103
161
|
)}
|
|
104
162
|
>
|
|
105
163
|
{!hasMessages && (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
164
|
+
<>
|
|
165
|
+
{!isConfigured && (
|
|
166
|
+
<div className="mx-3 mt-3 rounded-lg border border-border/60 bg-muted/40 px-3 py-2 text-[11px] leading-relaxed text-muted-foreground">
|
|
167
|
+
{t('chat.notConfigured')}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
<ChatSuggestions
|
|
171
|
+
suggestions={suggestions}
|
|
172
|
+
onSelect={(suggestion) => {
|
|
173
|
+
setInput('')
|
|
174
|
+
sendMessage(suggestion.prompt ?? suggestion.label)
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
</>
|
|
113
178
|
)}
|
|
114
179
|
{messages.map((msg) => (
|
|
115
180
|
<MessageBubble key={msg.id} message={msg} />
|
|
116
181
|
))}
|
|
117
|
-
{
|
|
182
|
+
{pendingAction && (
|
|
183
|
+
<ConfirmActionCard action={pendingAction} onResolve={resolvePendingAction} />
|
|
184
|
+
)}
|
|
185
|
+
{activeTools.length > 0 && (
|
|
186
|
+
<div className="flex flex-wrap items-center gap-1 px-2 py-1">
|
|
187
|
+
{activeTools.map((name) => (
|
|
188
|
+
<span
|
|
189
|
+
key={name}
|
|
190
|
+
className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 font-mono text-[10px] text-muted-foreground"
|
|
191
|
+
>
|
|
192
|
+
<Loader2 className="h-2.5 w-2.5 animate-spin" />
|
|
193
|
+
{name}
|
|
194
|
+
</span>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
{isStreaming && !pendingAction && activeTools.length === 0 && messages[messages.length - 1]?.content === '' && (
|
|
118
199
|
<div className="flex items-center gap-1 px-2 py-1.5">
|
|
119
200
|
<span className="h-1 w-1 rounded-full bg-muted-foreground/50 animate-pulse" />
|
|
120
201
|
<span className="h-1 w-1 rounded-full bg-muted-foreground/50 animate-pulse [animation-delay:150ms]" />
|
|
@@ -124,7 +205,7 @@ export function ChatPanel({
|
|
|
124
205
|
</div>
|
|
125
206
|
)}
|
|
126
207
|
|
|
127
|
-
{/* Input */}
|
|
208
|
+
{/* Input — disabled until a real chat.apiEndpoint is configured. */}
|
|
128
209
|
<form onSubmit={handleSubmit} className="border-t border-border/40 p-2">
|
|
129
210
|
<div className="flex items-center gap-1.5 rounded-full border border-border bg-background py-1 pl-3.5 pr-1 transition-colors focus-within:border-foreground/20">
|
|
130
211
|
<input
|
|
@@ -132,13 +213,19 @@ export function ChatPanel({
|
|
|
132
213
|
type="text"
|
|
133
214
|
value={input}
|
|
134
215
|
onChange={(e) => setInput(e.target.value)}
|
|
135
|
-
placeholder={
|
|
136
|
-
|
|
137
|
-
|
|
216
|
+
placeholder={
|
|
217
|
+
pendingAction
|
|
218
|
+
? t('chat.confirmAction.blocked')
|
|
219
|
+
: isConfigured
|
|
220
|
+
? t('chat.messagePlaceholder')
|
|
221
|
+
: t('chat.notConfigured')
|
|
222
|
+
}
|
|
223
|
+
className="min-w-0 flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground/50 focus:outline-none disabled:cursor-not-allowed"
|
|
224
|
+
disabled={isStreaming || !isConfigured || !!pendingAction}
|
|
138
225
|
/>
|
|
139
226
|
<button
|
|
140
227
|
type="submit"
|
|
141
|
-
disabled={!input.trim() || isStreaming}
|
|
228
|
+
disabled={!input.trim() || isStreaming || !isConfigured || !!pendingAction}
|
|
142
229
|
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-foreground text-background transition-opacity hover:opacity-80 disabled:opacity-20"
|
|
143
230
|
aria-label="Send message"
|
|
144
231
|
>
|
|
@@ -150,11 +237,68 @@ export function ChatPanel({
|
|
|
150
237
|
)
|
|
151
238
|
}
|
|
152
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Goto button for a record the reply concerns. The ref is semantic — the
|
|
242
|
+
* button only renders when THIS app's route map resolves the archetype (a
|
|
243
|
+
* missing link is correct; an invented one is a dead end — agents.md).
|
|
244
|
+
*/
|
|
245
|
+
function RecordLinkButton({ link }: { link: ChatRecordLink }) {
|
|
246
|
+
const router = useRouter()
|
|
247
|
+
if (!resolveEntityRoute(link.archetype, link.kind)) return null
|
|
248
|
+
const href = resolveEntityHref(link.id, link.archetype, link.kind)
|
|
249
|
+
return (
|
|
250
|
+
<button
|
|
251
|
+
type="button"
|
|
252
|
+
onClick={() => router.navigate(href)}
|
|
253
|
+
className="inline-flex items-center gap-1 rounded-full border border-border bg-background px-2.5 py-1 text-[11px] font-medium text-foreground transition-colors hover:bg-muted"
|
|
254
|
+
>
|
|
255
|
+
{link.label}
|
|
256
|
+
<ArrowUpRight className="h-3 w-3" />
|
|
257
|
+
</button>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function ToolCallRow({ call }: { call: ChatToolCall }) {
|
|
262
|
+
const [open, setOpen] = React.useState(false)
|
|
263
|
+
return (
|
|
264
|
+
<div className="rounded-md border border-border/60">
|
|
265
|
+
<button
|
|
266
|
+
type="button"
|
|
267
|
+
onClick={() => setOpen(!open)}
|
|
268
|
+
className="flex w-full items-center gap-1.5 px-2 py-1 text-left font-mono text-[10px] text-muted-foreground transition-colors hover:text-foreground"
|
|
269
|
+
>
|
|
270
|
+
<Wrench className="h-2.5 w-2.5 shrink-0" />
|
|
271
|
+
<span className="truncate">{call.name}</span>
|
|
272
|
+
<ChevronDown className={cn('ml-auto h-2.5 w-2.5 shrink-0 transition-transform', open && 'rotate-180')} />
|
|
273
|
+
</button>
|
|
274
|
+
{open && (
|
|
275
|
+
<div className="space-y-1 border-t border-border/60 px-2 py-1.5">
|
|
276
|
+
{call.args && (
|
|
277
|
+
<pre className="max-h-24 overflow-auto whitespace-pre-wrap break-all font-mono text-[9px] leading-snug text-muted-foreground">{call.args}</pre>
|
|
278
|
+
)}
|
|
279
|
+
{call.result && (
|
|
280
|
+
<pre className="max-h-40 overflow-auto whitespace-pre-wrap break-all rounded bg-muted/50 p-1.5 font-mono text-[9px] leading-snug text-foreground/80">{call.result}</pre>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
153
288
|
function MessageBubble({ message }: { message: ChatMessage }) {
|
|
154
289
|
const isUser = message.role === 'user'
|
|
155
290
|
|
|
156
291
|
return (
|
|
157
|
-
<div className={cn('flex', isUser ? '
|
|
292
|
+
<div className={cn('flex flex-col gap-1', isUser ? 'items-end' : 'items-start')}>
|
|
293
|
+
{/* Chronological order: the work happened BEFORE the reply — trace on
|
|
294
|
+
top, answer below, goto buttons (the next step) underneath. */}
|
|
295
|
+
{!isUser && (message.toolCalls?.length ?? 0) > 0 && (
|
|
296
|
+
<div className="flex max-w-[85%] flex-col gap-0.5 px-1">
|
|
297
|
+
{message.toolCalls!.map((call, i) => (
|
|
298
|
+
<ToolCallRow key={`${call.name}-${i}`} call={call} />
|
|
299
|
+
))}
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
158
302
|
<div
|
|
159
303
|
className={cn(
|
|
160
304
|
'max-w-[85%] rounded-2xl px-3 py-1.5 text-[13px] leading-relaxed',
|
|
@@ -163,8 +307,15 @@ function MessageBubble({ message }: { message: ChatMessage }) {
|
|
|
163
307
|
: 'rounded-bl-sm bg-muted text-foreground'
|
|
164
308
|
)}
|
|
165
309
|
>
|
|
166
|
-
{message.content}
|
|
310
|
+
{isUser ? message.content : <ChatMarkdown content={message.content} />}
|
|
167
311
|
</div>
|
|
312
|
+
{!isUser && (message.links?.length ?? 0) > 0 && (
|
|
313
|
+
<div className="flex max-w-[85%] flex-wrap gap-1 px-1">
|
|
314
|
+
{message.links!.map((link) => (
|
|
315
|
+
<RecordLinkButton key={link.id} link={link} />
|
|
316
|
+
))}
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
168
319
|
</div>
|
|
169
320
|
)
|
|
170
321
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { ShieldCheck } from 'lucide-react'
|
|
3
|
+
import { useTranslation } from '../../hooks/useTranslation'
|
|
4
|
+
import type { PendingAgentAction } from '../../stores/chat.store'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The human gate in front of an agent write: shows what the agent wants to do
|
|
8
|
+
* (tool + arguments) and blocks the conversation until the user decides —
|
|
9
|
+
* the FAB's native rendering of the confirmation step every channel has
|
|
10
|
+
* (WhatsApp asks "responda SIM", MCP uses elicitation).
|
|
11
|
+
*/
|
|
12
|
+
export function ConfirmActionCard({
|
|
13
|
+
action,
|
|
14
|
+
onResolve,
|
|
15
|
+
}: {
|
|
16
|
+
action: PendingAgentAction
|
|
17
|
+
onResolve: (approved: boolean) => void
|
|
18
|
+
}) {
|
|
19
|
+
const { t } = useTranslation()
|
|
20
|
+
const entries = Object.entries(action.params).filter(([, v]) => v !== undefined && v !== null)
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="mx-3 my-2 rounded-lg border border-border bg-muted/40 p-3 text-sm">
|
|
24
|
+
<div className="mb-1 flex items-center gap-2 font-medium text-foreground">
|
|
25
|
+
<ShieldCheck className="h-4 w-4 shrink-0" />
|
|
26
|
+
{t('chat.confirmAction.title')}
|
|
27
|
+
</div>
|
|
28
|
+
<div className="mb-2 text-xs text-muted-foreground">{action.title}</div>
|
|
29
|
+
{entries.length > 0 && (
|
|
30
|
+
<dl className="mb-3 space-y-0.5">
|
|
31
|
+
{entries.map(([key, value]) => (
|
|
32
|
+
<div key={key} className="flex gap-2 text-xs">
|
|
33
|
+
<dt className="shrink-0 font-mono text-muted-foreground">{key}</dt>
|
|
34
|
+
<dd className="truncate text-foreground">{String(value)}</dd>
|
|
35
|
+
</div>
|
|
36
|
+
))}
|
|
37
|
+
</dl>
|
|
38
|
+
)}
|
|
39
|
+
<div className="flex gap-2">
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
onClick={() => onResolve(true)}
|
|
43
|
+
className="inline-flex h-7 items-center rounded-md bg-foreground px-3 text-xs font-medium text-background transition-opacity hover:opacity-80"
|
|
44
|
+
>
|
|
45
|
+
{t('chat.confirmAction.confirm')}
|
|
46
|
+
</button>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={() => onResolve(false)}
|
|
50
|
+
className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted"
|
|
51
|
+
>
|
|
52
|
+
{t('chat.confirmAction.cancel')}
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
// Minimal markdown for chat bubbles — the subset agents actually emit:
|
|
4
|
+
// **bold**, *italic*, `code`, [links](url), - / 1. lists, ### headings and
|
|
5
|
+
// paragraphs. Built as React nodes (never innerHTML), so model output can't
|
|
6
|
+
// inject markup. Deliberately dependency-free: the saas package ships in every
|
|
7
|
+
// generated app and a full markdown library is ~30kb for four constructs.
|
|
8
|
+
|
|
9
|
+
function renderInline(text: string, keyBase: string): React.ReactNode[] {
|
|
10
|
+
const out: React.ReactNode[] = []
|
|
11
|
+
// Tokenize: code first (protects its content), then bold, italic, links.
|
|
12
|
+
const pattern = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*]+\*)|(\[[^\]]+\]\([^)\s]+\))/g
|
|
13
|
+
let last = 0
|
|
14
|
+
let match: RegExpExecArray | null
|
|
15
|
+
let i = 0
|
|
16
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
17
|
+
if (match.index > last) out.push(text.slice(last, match.index))
|
|
18
|
+
const token = match[0]
|
|
19
|
+
const key = `${keyBase}-${i++}`
|
|
20
|
+
if (token.startsWith('`')) {
|
|
21
|
+
out.push(
|
|
22
|
+
<code key={key} className="rounded bg-black/10 px-1 font-mono text-[0.85em] dark:bg-white/10">
|
|
23
|
+
{token.slice(1, -1)}
|
|
24
|
+
</code>,
|
|
25
|
+
)
|
|
26
|
+
} else if (token.startsWith('**')) {
|
|
27
|
+
out.push(<strong key={key}>{renderInline(token.slice(2, -2), key)}</strong>)
|
|
28
|
+
} else if (token.startsWith('*')) {
|
|
29
|
+
out.push(<em key={key}>{renderInline(token.slice(1, -1), key)}</em>)
|
|
30
|
+
} else {
|
|
31
|
+
const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(token)
|
|
32
|
+
if (link && /^(https?:\/\/|\/|#)/.test(link[2])) {
|
|
33
|
+
out.push(
|
|
34
|
+
<a key={key} href={link[2]} target="_blank" rel="noreferrer" className="underline underline-offset-2">
|
|
35
|
+
{renderInline(link[1], key)}
|
|
36
|
+
</a>,
|
|
37
|
+
)
|
|
38
|
+
} else {
|
|
39
|
+
out.push(token)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
last = match.index + token.length
|
|
43
|
+
}
|
|
44
|
+
if (last < text.length) out.push(text.slice(last))
|
|
45
|
+
return out
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ChatMarkdown({ content }: { content: string }) {
|
|
49
|
+
const blocks: React.ReactNode[] = []
|
|
50
|
+
const lines = content.split('\n')
|
|
51
|
+
let i = 0
|
|
52
|
+
let key = 0
|
|
53
|
+
|
|
54
|
+
while (i < lines.length) {
|
|
55
|
+
const line = lines[i]
|
|
56
|
+
if (!line.trim()) {
|
|
57
|
+
i++
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
const heading = /^(#{1,4})\s+(.*)$/.exec(line)
|
|
61
|
+
if (heading) {
|
|
62
|
+
blocks.push(
|
|
63
|
+
<p key={key++} className="mt-1.5 font-semibold first:mt-0">
|
|
64
|
+
{renderInline(heading[2], `h${key}`)}
|
|
65
|
+
</p>,
|
|
66
|
+
)
|
|
67
|
+
i++
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
if (/^\s*([-*]|\d+[.)])\s+/.test(line)) {
|
|
71
|
+
const items: React.ReactNode[] = []
|
|
72
|
+
const ordered = /^\s*\d+[.)]\s+/.test(line)
|
|
73
|
+
while (i < lines.length && /^\s*([-*]|\d+[.)])\s+/.test(lines[i])) {
|
|
74
|
+
const item = lines[i].replace(/^\s*([-*]|\d+[.)])\s+/, '')
|
|
75
|
+
items.push(<li key={items.length}>{renderInline(item, `li${key}-${items.length}`)}</li>)
|
|
76
|
+
i++
|
|
77
|
+
}
|
|
78
|
+
const listClass = 'my-1 space-y-0.5 pl-4 first:mt-0 last:mb-0'
|
|
79
|
+
blocks.push(
|
|
80
|
+
ordered ? (
|
|
81
|
+
<ol key={key++} className={`${listClass} list-decimal`}>{items}</ol>
|
|
82
|
+
) : (
|
|
83
|
+
<ul key={key++} className={`${listClass} list-disc`}>{items}</ul>
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
// Paragraph: swallow consecutive plain lines with soft breaks.
|
|
89
|
+
const para: React.ReactNode[] = []
|
|
90
|
+
while (i < lines.length && lines[i].trim() && !/^\s*([-*]|\d+[.)])\s+/.test(lines[i]) && !/^#{1,4}\s+/.test(lines[i])) {
|
|
91
|
+
if (para.length) para.push(<br key={`br${key}-${para.length}`} />)
|
|
92
|
+
para.push(...renderInline(lines[i], `p${key}-${para.length}`))
|
|
93
|
+
i++
|
|
94
|
+
}
|
|
95
|
+
blocks.push(
|
|
96
|
+
<p key={key++} className="my-1 first:mt-0 last:mb-0">
|
|
97
|
+
{para}
|
|
98
|
+
</p>,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
return <>{blocks}</>
|
|
102
|
+
}
|
|
@@ -23,9 +23,10 @@ import { useOrgAdapterOptional as useNativeOrgAdapter } from '../../../org'
|
|
|
23
23
|
import type { OrgAdapter } from '../../types/org-adapter'
|
|
24
24
|
const useOrgAdapterOptional = () => useNativeOrgAdapter() as unknown as OrgAdapter | null
|
|
25
25
|
import { useAuthStore } from '@fayz-ai/auth'
|
|
26
|
-
import {
|
|
26
|
+
import { useSystemPermission } from '../../hooks/useSystemPermission'
|
|
27
27
|
import { useTranslation } from '../../hooks/useTranslation'
|
|
28
28
|
import { dedup } from '../../lib/dedup'
|
|
29
|
+
import { useLimitGuard, invalidateLimit } from '../../../access'
|
|
29
30
|
|
|
30
31
|
export function TeamTab() {
|
|
31
32
|
const adapter = useOrgAdapterOptional()
|
|
@@ -36,9 +37,17 @@ export function TeamTab() {
|
|
|
36
37
|
const invites = useInviteStore((s) => s.invites)
|
|
37
38
|
const setInvites = useInviteStore((s) => s.setInvites)
|
|
38
39
|
const { user } = useAuthStore()
|
|
39
|
-
const
|
|
40
|
+
const hasSystemPermission = useSystemPermission()
|
|
40
41
|
|
|
41
42
|
const [inviteOpen, setInviteOpen] = React.useState(false)
|
|
43
|
+
// Seat cap ('users' limit → tenant_members count). Guarded before opening the
|
|
44
|
+
// invite dialog; the guard opens the global UpgradeModal when at the cap.
|
|
45
|
+
const guardSeats = useLimitGuard('users')
|
|
46
|
+
|
|
47
|
+
const handleOpenInvite = async () => {
|
|
48
|
+
if ((await guardSeats(1)) === 'blocked') return
|
|
49
|
+
setInviteOpen(true)
|
|
50
|
+
}
|
|
42
51
|
|
|
43
52
|
// Load invites on mount (deduped to avoid strict mode double-fetch)
|
|
44
53
|
React.useEffect(() => {
|
|
@@ -107,7 +116,7 @@ export function TeamTab() {
|
|
|
107
116
|
<p className="text-sm text-muted-foreground">{t('organization.team.memberCount', { count: String(members.length), plural: members.length !== 1 ? 's' : '' })}</p>
|
|
108
117
|
</div>
|
|
109
118
|
{canManageTeam && (
|
|
110
|
-
<Button size="sm" onClick={() =>
|
|
119
|
+
<Button size="sm" onClick={() => { void handleOpenInvite() }}>
|
|
111
120
|
<UserPlus className="h-4 w-4 mr-1" />
|
|
112
121
|
{t('organization.team.invite')}
|
|
113
122
|
</Button>
|
|
@@ -264,7 +273,15 @@ export function TeamTab() {
|
|
|
264
273
|
</div>
|
|
265
274
|
)}
|
|
266
275
|
|
|
267
|
-
<InviteMemberDialog
|
|
276
|
+
<InviteMemberDialog
|
|
277
|
+
open={inviteOpen}
|
|
278
|
+
onOpenChange={(open) => {
|
|
279
|
+
setInviteOpen(open)
|
|
280
|
+
// On close (after a successful invite the dialog closes itself),
|
|
281
|
+
// refresh the seat count so the guard/banner reflect the new member.
|
|
282
|
+
if (!open) invalidateLimit('users')
|
|
283
|
+
}}
|
|
284
|
+
/>
|
|
268
285
|
</div>
|
|
269
286
|
)
|
|
270
287
|
}
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
} from '@fayz-ai/ui'
|
|
12
12
|
import { cn } from '../../lib/cn'
|
|
13
13
|
import { useTranslation } from '../../hooks/useTranslation'
|
|
14
|
-
|
|
14
|
+
// Native org adapter — the shell lib/org-context provider is never mounted, so
|
|
15
|
+
// the shell useOrgAdapter() threw here. The native <OrgProvider> is the mounted one.
|
|
16
|
+
import { useOrgAdapter } from '../../../org'
|
|
15
17
|
import { useAuthStore } from '../../stores/auth.store'
|
|
16
18
|
import { useOrganizationStore } from '../../stores/organization.store'
|
|
17
19
|
import { usePermissionsStore } from '../../stores/permissions.store'
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { OrgSwitcher } from './OrgSwitcher'
|
|
2
2
|
export { OrgInitializer } from './OrgInitializer'
|
|
3
|
-
export { PermissionGate } from './PermissionGate'
|
|
4
3
|
export { PermissionMatrixEditor } from './PermissionMatrixEditor'
|
|
5
4
|
export { PermissionProfilesTab } from './PermissionProfilesTab'
|
|
6
5
|
export { TeamTab } from './TeamTab'
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { CompanySettings } from './CompanySettings'
|
|
2
2
|
import { useOrganizationStore } from '../../stores/organization.store'
|
|
3
|
-
|
|
3
|
+
// Native org adapter — the shell's lib/org-context provider is never mounted,
|
|
4
|
+
// so importing from there returned a null adapter and every save was a no-op.
|
|
5
|
+
// The native <OrgProvider> (packages/saas/src/org) is the one actually mounted.
|
|
6
|
+
import { useOrgAdapterOptional } from '../../../org'
|
|
4
7
|
import { toast } from '../notifications/ToastProvider'
|
|
5
8
|
import type { Tenant, TenantSettings } from '../../types'
|
|
6
9
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { FieldRulesSettings } from './FieldRulesSettings'
|
|
2
2
|
import { useOrganizationStore } from '../../stores/organization.store'
|
|
3
|
-
|
|
3
|
+
// Native org adapter — see ConnectedCompanySettings. The shell org-context
|
|
4
|
+
// provider is never mounted, so the old import yielded a null adapter (no-op save).
|
|
5
|
+
import { useOrgAdapterOptional } from '../../../org'
|
|
4
6
|
import { getRegisteredEntities } from '../../lib/entity-registry'
|
|
5
7
|
import { toast } from '../notifications/ToastProvider'
|
|
6
8
|
import { useTranslation } from '../../hooks/useTranslation'
|
|
@@ -3,7 +3,9 @@ import type { TenantSecuritySettings } from './SecuritySettings'
|
|
|
3
3
|
import { useAuthStore } from '../../stores/auth.store'
|
|
4
4
|
import { useOrganizationStore } from '../../stores/organization.store'
|
|
5
5
|
import { useAuthAdapterOptional } from '../../lib/auth-context'
|
|
6
|
-
|
|
6
|
+
// Native org adapter — see ConnectedCompanySettings. The shell org-context
|
|
7
|
+
// provider is never mounted, so the old import yielded a null adapter (no-op save).
|
|
8
|
+
import { useOrgAdapterOptional } from '../../../org'
|
|
7
9
|
import { toast } from '../notifications/ToastProvider'
|
|
8
10
|
|
|
9
11
|
export function ConnectedSecuritySettings() {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createCrudPage } from '../../../crud/createCrudPage'
|
|
2
|
-
|
|
2
|
+
// Core's EntityDef (the type createCrudPage consumes) carries `limitKey` for
|
|
3
|
+
// plan quantity gating; the shell alias does not.
|
|
4
|
+
import type { EntityDef } from '@fayz-ai/core'
|
|
3
5
|
|
|
4
6
|
export const locationEntityDef: EntityDef = {
|
|
5
7
|
name: 'Location',
|
|
@@ -9,6 +11,9 @@ export const locationEntityDef: EntityDef = {
|
|
|
9
11
|
displayField: 'name',
|
|
10
12
|
subtitleField: 'city',
|
|
11
13
|
defaultSort: 'name',
|
|
14
|
+
// Plan quantity cap — the generic CRUD (CrudFormPage/CrudPage/CrudListView)
|
|
15
|
+
// guards create/import and dims "+ Add" at the cap. Core declares 'locations'.
|
|
16
|
+
limitKey: 'locations',
|
|
12
17
|
fields: [
|
|
13
18
|
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true, searchable: true },
|
|
14
19
|
{ key: 'phone', label: 'Phone', type: 'phone', showInTable: true },
|