@open-mercato/core 0.6.1-develop.3276.1.1fcd145078 → 0.6.1-develop.3287.1.450f4ffb56
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/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/domain_mapping/index.js +31 -0
- package/dist/generated/entities/domain_mapping/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +16 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/auth.js +1 -1
- package/dist/helpers/integration/auth.js.map +2 -2
- package/dist/helpers/integration/authUi.js +1 -1
- package/dist/helpers/integration/authUi.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +18 -17
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/auth/frontend/login.js +4 -4
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/auth/frontend/reset/[token]/page.js +5 -5
- package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
- package/dist/modules/auth/frontend/reset.js +2 -2
- package/dist/modules/auth/frontend/reset.js.map +2 -2
- package/dist/modules/customer_accounts/acl.js +2 -1
- package/dist/modules/customer_accounts/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js +110 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js +114 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.js.map +7 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js +271 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-check.js +70 -0
- package/dist/modules/customer_accounts/api/domain-check.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js +128 -0
- package/dist/modules/customer_accounts/api/domain-resolve/all.js.map +7 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js +84 -0
- package/dist/modules/customer_accounts/api/domain-resolve.js.map +7 -0
- package/dist/modules/customer_accounts/api/login.js +14 -3
- package/dist/modules/customer_accounts/api/login.js.map +2 -2
- package/dist/modules/customer_accounts/api/magic-link/request.js +14 -3
- package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-request.js +14 -3
- package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
- package/dist/modules/customer_accounts/api/signup.js +32 -5
- package/dist/modules/customer_accounts/api/signup.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js +48 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +43 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js +53 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js +69 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js +31 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js +147 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js +39 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js +60 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js +11 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js +1 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +361 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js +28 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.js.map +7 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +4 -3
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +6 -5
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/data/enrichers.js +78 -12
- package/dist/modules/customer_accounts/data/enrichers.js.map +2 -2
- package/dist/modules/customer_accounts/data/entities.js +85 -2
- package/dist/modules/customer_accounts/data/entities.js.map +2 -2
- package/dist/modules/customer_accounts/data/guards.js +130 -0
- package/dist/modules/customer_accounts/data/guards.js.map +7 -0
- package/dist/modules/customer_accounts/data/validators.js +19 -0
- package/dist/modules/customer_accounts/data/validators.js.map +2 -2
- package/dist/modules/customer_accounts/di.js +23 -1
- package/dist/modules/customer_accounts/di.js.map +2 -2
- package/dist/modules/customer_accounts/events.js +9 -2
- package/dist/modules/customer_accounts/events.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +27 -7
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerUrl.js +48 -0
- package/dist/modules/customer_accounts/lib/customerUrl.js.map +7 -0
- package/dist/modules/customer_accounts/lib/hostname.js +55 -0
- package/dist/modules/customer_accounts/lib/hostname.js.map +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js +7 -0
- package/dist/modules/customer_accounts/lib/platformDomains.js.map +7 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js +71 -0
- package/dist/modules/customer_accounts/lib/proxyRanges.js.map +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js +7 -0
- package/dist/modules/customer_accounts/lib/rateLimiter.js.map +2 -2
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js +63 -0
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +7 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js +13 -0
- package/dist/modules/customer_accounts/lib/secretCompare.js.map +7 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js +37 -0
- package/dist/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.js.map +7 -0
- package/dist/modules/customer_accounts/notifications.client.js +76 -0
- package/dist/modules/customer_accounts/notifications.client.js.map +2 -2
- package/dist/modules/customer_accounts/notifications.js +76 -0
- package/dist/modules/customer_accounts/notifications.js.map +2 -2
- package/dist/modules/customer_accounts/services/domainMappingService.js +497 -0
- package/dist/modules/customer_accounts/services/domainMappingService.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +48 -1
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js +29 -0
- package/dist/modules/customer_accounts/subscribers/invalidateDomainCache.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainActivated.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js +30 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainDnsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js +31 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainTlsFailed.js.map +7 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js +29 -0
- package/dist/modules/customer_accounts/subscribers/notifyDomainVerified.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js +67 -0
- package/dist/modules/customer_accounts/workers/domainTlsRetryWorker.js.map +7 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js +34 -0
- package/dist/modules/customer_accounts/workers/domainVerificationWorker.js.map +7 -0
- package/dist/modules/customers/api/companies/route.js +4 -9
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +4 -9
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +4 -9
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/api/utils.js +0 -12
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +361 -284
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +378 -473
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +368 -286
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +15 -1
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +2 -2
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +188 -36
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +2 -2
- package/dist/modules/customers/widgets/injection-table.js +6 -0
- package/dist/modules/customers/widgets/injection-table.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +2 -3
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +16 -2
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +5 -3
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +3 -3
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +4 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +31 -8
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +3 -5
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +3 -4
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +3 -4
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/generated/entities/domain_mapping/index.ts +14 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +16 -0
- package/package.json +5 -5
- package/src/helpers/integration/auth.ts +1 -1
- package/src/helpers/integration/authUi.ts +1 -1
- package/src/helpers/integration/salesUi.ts +27 -23
- package/src/modules/auth/frontend/login.tsx +4 -3
- package/src/modules/auth/frontend/reset/[token]/page.tsx +4 -4
- package/src/modules/auth/frontend/reset.tsx +2 -2
- package/src/modules/customer_accounts/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/health-check.ts +119 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings/[id]/verify.ts +123 -0
- package/src/modules/customer_accounts/api/admin/domain-mappings.ts +303 -0
- package/src/modules/customer_accounts/api/domain-check.ts +83 -0
- package/src/modules/customer_accounts/api/domain-resolve/all.ts +139 -0
- package/src/modules/customer_accounts/api/domain-resolve.ts +91 -0
- package/src/modules/customer_accounts/api/login.ts +14 -3
- package/src/modules/customer_accounts/api/magic-link/request.ts +14 -3
- package/src/modules/customer_accounts/api/password/reset-request.ts +15 -3
- package/src/modules/customer_accounts/api/signup.ts +36 -5
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Actions.tsx +70 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +66 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DnsConfig.tsx +78 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/DomainPanel.tsx +90 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/OrgSwitcher.tsx +43 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/RegisterDialog.tsx +178 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/StatusBadgeSection.tsx +56 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Stepper.tsx +69 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/domainStatusMap.ts +10 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/types.ts +31 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.meta.ts +26 -0
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +411 -0
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +3 -2
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +5 -4
- package/src/modules/customer_accounts/data/enrichers.ts +95 -12
- package/src/modules/customer_accounts/data/entities.ts +80 -1
- package/src/modules/customer_accounts/data/guards.ts +155 -0
- package/src/modules/customer_accounts/data/validators.ts +26 -0
- package/src/modules/customer_accounts/di.ts +35 -1
- package/src/modules/customer_accounts/events.ts +8 -1
- package/src/modules/customer_accounts/i18n/de.json +78 -0
- package/src/modules/customer_accounts/i18n/en.json +78 -0
- package/src/modules/customer_accounts/i18n/es.json +78 -0
- package/src/modules/customer_accounts/i18n/pl.json +78 -0
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +44 -8
- package/src/modules/customer_accounts/lib/customerUrl.ts +72 -0
- package/src/modules/customer_accounts/lib/hostname.ts +76 -0
- package/src/modules/customer_accounts/lib/platformDomains.ts +14 -0
- package/src/modules/customer_accounts/lib/proxyRanges.ts +85 -0
- package/src/modules/customer_accounts/lib/rateLimiter.ts +10 -0
- package/src/modules/customer_accounts/lib/resolveTenantContext.ts +96 -0
- package/src/modules/customer_accounts/lib/secretCompare.ts +20 -0
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +341 -0
- package/src/modules/customer_accounts/migrations/Migration20260430120000_customer_accounts.ts +38 -0
- package/src/modules/customer_accounts/notifications.client.ts +76 -0
- package/src/modules/customer_accounts/notifications.ts +76 -0
- package/src/modules/customer_accounts/services/domainMappingService.ts +632 -0
- package/src/modules/customer_accounts/setup.ts +81 -1
- package/src/modules/customer_accounts/subscribers/invalidateDomainCache.ts +35 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainActivated.ts +32 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainDnsFailed.ts +33 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainTlsFailed.ts +34 -0
- package/src/modules/customer_accounts/subscribers/notifyDomainVerified.ts +32 -0
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
- package/src/modules/customer_accounts/workers/domainTlsRetryWorker.ts +84 -0
- package/src/modules/customer_accounts/workers/domainVerificationWorker.ts +45 -0
- package/src/modules/customers/api/companies/route.ts +4 -9
- package/src/modules/customers/api/deals/route.ts +4 -9
- package/src/modules/customers/api/people/route.ts +4 -9
- package/src/modules/customers/api/utils.ts +0 -14
- package/src/modules/customers/backend/customers/companies/page.tsx +321 -237
- package/src/modules/customers/backend/customers/deals/page.tsx +321 -455
- package/src/modules/customers/backend/customers/people/page.tsx +331 -240
- package/src/modules/customers/components/detail/assignableStaff.ts +21 -0
- package/src/modules/customers/components/formConfig.tsx +2 -2
- package/src/modules/customers/i18n/de.json +31 -0
- package/src/modules/customers/i18n/en.json +31 -0
- package/src/modules/customers/i18n/es.json +31 -0
- package/src/modules/customers/i18n/pl.json +31 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +197 -40
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +13 -7
- package/src/modules/customers/widgets/injection-table.ts +6 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +2 -2
- package/src/modules/perspectives/services/perspectiveService.ts +30 -2
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +5 -3
- package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +3 -3
- package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +4 -2
- package/src/modules/query_index/lib/engine.ts +46 -8
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +3 -5
- package/src/modules/sales/components/ProviderFieldInput.tsx +2 -2
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +2 -3
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +3 -4
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +2 -2
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +2 -2
|
@@ -37,7 +37,10 @@ import { cn } from '@open-mercato/shared/lib/utils'
|
|
|
37
37
|
|
|
38
38
|
export const CUSTOMERS_AI_INJECT_AGENT_ID = 'customers.account_assistant'
|
|
39
39
|
|
|
40
|
-
export type CustomersAiInjectView =
|
|
40
|
+
export type CustomersAiInjectView =
|
|
41
|
+
| 'customers.people.list'
|
|
42
|
+
| 'customers.companies.list'
|
|
43
|
+
| 'customers.deals.list'
|
|
41
44
|
|
|
42
45
|
export interface CustomersAiInjectPageContext {
|
|
43
46
|
view: CustomersAiInjectView
|
|
@@ -83,8 +86,9 @@ function readNumber(value: unknown): number {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
function resolveView(tableId: string | null | undefined): CustomersAiInjectView {
|
|
86
|
-
if (typeof tableId === 'string'
|
|
87
|
-
return 'customers.
|
|
89
|
+
if (typeof tableId === 'string') {
|
|
90
|
+
if (tableId.includes('deals')) return 'customers.deals.list'
|
|
91
|
+
if (tableId.includes('companies')) return 'customers.companies.list'
|
|
88
92
|
}
|
|
89
93
|
return 'customers.people.list'
|
|
90
94
|
}
|
|
@@ -115,6 +119,78 @@ function useCustomerSuggestions(
|
|
|
115
119
|
): AiChatSuggestion[] {
|
|
116
120
|
const t = useT()
|
|
117
121
|
return React.useMemo(() => {
|
|
122
|
+
if (view === 'customers.deals.list') {
|
|
123
|
+
if (hasSelection) {
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
label: t(
|
|
127
|
+
'customers.ai_assistant.suggestions.summarizeSelectedDeals',
|
|
128
|
+
'Summarize selected deals',
|
|
129
|
+
),
|
|
130
|
+
prompt: `Give me a summary of my ${selectedCount} selected deals — stage, value, and close date`,
|
|
131
|
+
icon: <Handshake className="size-4" />,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
label: t(
|
|
135
|
+
'customers.ai_assistant.suggestions.atRiskSelectedDeals',
|
|
136
|
+
'Selected deals at risk',
|
|
137
|
+
),
|
|
138
|
+
prompt: `Of my ${selectedCount} selected deals, which look at risk of slipping their close date?`,
|
|
139
|
+
icon: <Handshake className="size-4" />,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: t(
|
|
143
|
+
'customers.ai_assistant.suggestions.companiesForSelectedDeals',
|
|
144
|
+
'Companies on selected deals',
|
|
145
|
+
),
|
|
146
|
+
prompt: `List the companies associated with my ${selectedCount} selected deals`,
|
|
147
|
+
icon: <Building2 className="size-4" />,
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
return [
|
|
152
|
+
{
|
|
153
|
+
label: t(
|
|
154
|
+
'customers.ai_assistant.suggestions.dealsClosingThisMonth',
|
|
155
|
+
'Deals closing this month',
|
|
156
|
+
),
|
|
157
|
+
prompt: 'Which deals are closing this month?',
|
|
158
|
+
icon: <Handshake className="size-4" />,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
label: t(
|
|
162
|
+
'customers.ai_assistant.suggestions.dealsAtRisk',
|
|
163
|
+
'Deals at risk of slipping',
|
|
164
|
+
),
|
|
165
|
+
prompt: 'Show me deals at risk of slipping their close date',
|
|
166
|
+
icon: <Handshake className="size-4" />,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
label: t(
|
|
170
|
+
'customers.ai_assistant.suggestions.totalOpenDealValue',
|
|
171
|
+
'Total open deal value',
|
|
172
|
+
),
|
|
173
|
+
prompt: "What's the total value of open deals across the pipeline?",
|
|
174
|
+
icon: <Handshake className="size-4" />,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
label: t(
|
|
178
|
+
'customers.ai_assistant.suggestions.topDealsByValue',
|
|
179
|
+
'Top deals by value',
|
|
180
|
+
),
|
|
181
|
+
prompt: 'Show me the top 5 deals by value',
|
|
182
|
+
icon: <Handshake className="size-4" />,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
label: t(
|
|
186
|
+
'customers.ai_assistant.suggestions.searchDeals',
|
|
187
|
+
'Search for a deal',
|
|
188
|
+
),
|
|
189
|
+
prompt: 'Search for deals by title, company, or stage',
|
|
190
|
+
icon: <Search className="size-4" />,
|
|
191
|
+
},
|
|
192
|
+
]
|
|
193
|
+
}
|
|
118
194
|
if (view === 'customers.companies.list') {
|
|
119
195
|
if (hasSelection) {
|
|
120
196
|
return [
|
|
@@ -228,24 +304,115 @@ function useCustomerContextItems(pageContext: CustomersAiInjectPageContext): AiC
|
|
|
228
304
|
return React.useMemo(() => {
|
|
229
305
|
const items: AiChatContextItem[] = []
|
|
230
306
|
const { selectedCount, totalMatching } = pageContext.extra
|
|
231
|
-
const
|
|
307
|
+
const view = pageContext.view
|
|
308
|
+
let selectedKey = 'customers.ai_assistant.context.selectedPeople'
|
|
309
|
+
let selectedFallback = '{count} contacts selected'
|
|
310
|
+
let matchingKey = 'customers.ai_assistant.context.matchingPeople'
|
|
311
|
+
let matchingFallback = '{count} contacts in view'
|
|
312
|
+
if (view === 'customers.companies.list') {
|
|
313
|
+
selectedKey = 'customers.ai_assistant.context.selectedCompanies'
|
|
314
|
+
selectedFallback = '{count} companies selected'
|
|
315
|
+
matchingKey = 'customers.ai_assistant.context.matchingCompanies'
|
|
316
|
+
matchingFallback = '{count} companies in view'
|
|
317
|
+
} else if (view === 'customers.deals.list') {
|
|
318
|
+
selectedKey = 'customers.ai_assistant.context.selectedDeals'
|
|
319
|
+
selectedFallback = '{count} deals selected'
|
|
320
|
+
matchingKey = 'customers.ai_assistant.context.matchingDeals'
|
|
321
|
+
matchingFallback = '{count} deals in view'
|
|
322
|
+
}
|
|
232
323
|
if (selectedCount > 0) {
|
|
233
|
-
|
|
234
|
-
? 'customers.ai_assistant.context.selectedCompanies'
|
|
235
|
-
: 'customers.ai_assistant.context.selectedPeople'
|
|
236
|
-
const fallback = isCompanies ? '{count} companies selected' : '{count} contacts selected'
|
|
237
|
-
items.push({ label: t(key, fallback).replace('{count}', String(selectedCount)) })
|
|
324
|
+
items.push({ label: t(selectedKey, selectedFallback).replace('{count}', String(selectedCount)) })
|
|
238
325
|
} else if (totalMatching > 0) {
|
|
239
|
-
|
|
240
|
-
? 'customers.ai_assistant.context.matchingCompanies'
|
|
241
|
-
: 'customers.ai_assistant.context.matchingPeople'
|
|
242
|
-
const fallback = isCompanies ? '{count} companies in view' : '{count} contacts in view'
|
|
243
|
-
items.push({ label: t(key, fallback).replace('{count}', String(totalMatching)) })
|
|
326
|
+
items.push({ label: t(matchingKey, matchingFallback).replace('{count}', String(totalMatching)) })
|
|
244
327
|
}
|
|
245
328
|
return items
|
|
246
329
|
}, [pageContext, t])
|
|
247
330
|
}
|
|
248
331
|
|
|
332
|
+
interface ViewCopy {
|
|
333
|
+
welcomeDescriptionAll: string
|
|
334
|
+
welcomeDescriptionSelection: string
|
|
335
|
+
descriptionWithSelection: string
|
|
336
|
+
triggerAriaLabel: string
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function useViewCopy(view: CustomersAiInjectView, selectedCount: number): ViewCopy {
|
|
340
|
+
const t = useT()
|
|
341
|
+
return React.useMemo(() => {
|
|
342
|
+
const replaceCount = (s: string) => s.replace('{count}', String(selectedCount))
|
|
343
|
+
if (view === 'customers.deals.list') {
|
|
344
|
+
return {
|
|
345
|
+
welcomeDescriptionAll: t(
|
|
346
|
+
'customers.ai_assistant.sheet.welcomeDescriptionAllDeals',
|
|
347
|
+
'Ask about deals, pipeline, or close dates:',
|
|
348
|
+
),
|
|
349
|
+
welcomeDescriptionSelection: replaceCount(
|
|
350
|
+
t(
|
|
351
|
+
'customers.ai_assistant.sheet.welcomeDescriptionSelectionDeals',
|
|
352
|
+
'Ready to explore your {count} selected deals:',
|
|
353
|
+
),
|
|
354
|
+
),
|
|
355
|
+
descriptionWithSelection: replaceCount(
|
|
356
|
+
t(
|
|
357
|
+
'customers.ai_assistant.sheet.descriptionWithSelectionDeals',
|
|
358
|
+
'Working with {count} selected deals. Ask about their stage, value, and close dates.',
|
|
359
|
+
),
|
|
360
|
+
),
|
|
361
|
+
triggerAriaLabel: t(
|
|
362
|
+
'customers.ai_assistant.trigger.ariaLabelDeals',
|
|
363
|
+
'Open AI assistant for deals',
|
|
364
|
+
),
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (view === 'customers.companies.list') {
|
|
368
|
+
return {
|
|
369
|
+
welcomeDescriptionAll: t(
|
|
370
|
+
'customers.ai_assistant.sheet.welcomeDescriptionAllCompanies',
|
|
371
|
+
'Ask me anything about your companies, deals, and activities:',
|
|
372
|
+
),
|
|
373
|
+
welcomeDescriptionSelection: replaceCount(
|
|
374
|
+
t(
|
|
375
|
+
'customers.ai_assistant.sheet.welcomeDescriptionSelectionCompanies',
|
|
376
|
+
'Ready to explore your {count} selected companies:',
|
|
377
|
+
),
|
|
378
|
+
),
|
|
379
|
+
descriptionWithSelection: replaceCount(
|
|
380
|
+
t(
|
|
381
|
+
'customers.ai_assistant.sheet.descriptionWithSelectionCompanies',
|
|
382
|
+
'Working with {count} selected companies. Ask about their deals, contacts, and activities.',
|
|
383
|
+
),
|
|
384
|
+
),
|
|
385
|
+
triggerAriaLabel: t(
|
|
386
|
+
'customers.ai_assistant.trigger.ariaLabelCompanies',
|
|
387
|
+
'Open AI assistant for companies',
|
|
388
|
+
),
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
welcomeDescriptionAll: t(
|
|
393
|
+
'customers.ai_assistant.sheet.welcomeDescriptionAll',
|
|
394
|
+
'Ask me anything about your customers, companies, and deals:',
|
|
395
|
+
),
|
|
396
|
+
welcomeDescriptionSelection: replaceCount(
|
|
397
|
+
t(
|
|
398
|
+
'customers.ai_assistant.sheet.welcomeDescriptionSelection',
|
|
399
|
+
'Ready to explore your {count} selected contacts:',
|
|
400
|
+
),
|
|
401
|
+
),
|
|
402
|
+
descriptionWithSelection: replaceCount(
|
|
403
|
+
t(
|
|
404
|
+
'customers.ai_assistant.sheet.descriptionWithSelection',
|
|
405
|
+
'Working with {count} selected contacts. Ask about their details, deals, companies, and activities.',
|
|
406
|
+
),
|
|
407
|
+
),
|
|
408
|
+
triggerAriaLabel: t(
|
|
409
|
+
'customers.ai_assistant.trigger.ariaLabel',
|
|
410
|
+
'Open AI assistant for people',
|
|
411
|
+
),
|
|
412
|
+
}
|
|
413
|
+
}, [view, selectedCount, t])
|
|
414
|
+
}
|
|
415
|
+
|
|
249
416
|
interface CustomerAgentDescriptor {
|
|
250
417
|
id: string
|
|
251
418
|
label: string
|
|
@@ -285,6 +452,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
285
452
|
const hasSelection = selectedCount > 0
|
|
286
453
|
const suggestions = useCustomerSuggestions(pageContext.view, hasSelection, selectedCount)
|
|
287
454
|
const contextItems = useCustomerContextItems(pageContext)
|
|
455
|
+
const viewCopy = useViewCopy(pageContext.view, selectedCount)
|
|
288
456
|
|
|
289
457
|
const openAgent = React.useCallback((agentId: string) => {
|
|
290
458
|
setActiveAgent(agentId)
|
|
@@ -330,14 +498,8 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
330
498
|
contextItems,
|
|
331
499
|
welcomeTitle: t('customers.ai_assistant.sheet.welcomeTitle', 'CRM Assistant'),
|
|
332
500
|
welcomeDescription: hasSelection
|
|
333
|
-
?
|
|
334
|
-
|
|
335
|
-
'Ready to explore your {count} selected contacts:',
|
|
336
|
-
).replace('{count}', String(selectedCount))
|
|
337
|
-
: t(
|
|
338
|
-
'customers.ai_assistant.sheet.welcomeDescriptionAll',
|
|
339
|
-
'Ask me anything about your customers, companies, and deals:',
|
|
340
|
-
),
|
|
501
|
+
? viewCopy.welcomeDescriptionSelection
|
|
502
|
+
: viewCopy.welcomeDescriptionAll,
|
|
341
503
|
})
|
|
342
504
|
setOpen(false)
|
|
343
505
|
}, [
|
|
@@ -347,15 +509,12 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
347
509
|
dock,
|
|
348
510
|
hasSelection,
|
|
349
511
|
pageContext,
|
|
350
|
-
selectedCount,
|
|
351
512
|
suggestions,
|
|
352
513
|
t,
|
|
514
|
+
viewCopy,
|
|
353
515
|
])
|
|
354
516
|
|
|
355
|
-
const triggerLabel =
|
|
356
|
-
'customers.ai_assistant.trigger.ariaLabel',
|
|
357
|
-
'Open AI assistant for people',
|
|
358
|
-
)
|
|
517
|
+
const triggerLabel = viewCopy.triggerAriaLabel
|
|
359
518
|
|
|
360
519
|
const labelText = t('customers.ai_assistant.trigger.label', 'AI')
|
|
361
520
|
const moreAgentsLabel = t(
|
|
@@ -375,10 +534,14 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
375
534
|
title={triggerLabel}
|
|
376
535
|
className={cn(
|
|
377
536
|
'relative',
|
|
537
|
+
'hover:bg-brand-violet/10',
|
|
378
538
|
agents.length > 1 && 'rounded-r-none border-r-0',
|
|
379
539
|
)}
|
|
380
540
|
>
|
|
381
|
-
<Sparkles
|
|
541
|
+
<Sparkles
|
|
542
|
+
className="size-4 text-brand-violet"
|
|
543
|
+
aria-hidden
|
|
544
|
+
/>
|
|
382
545
|
<span>{labelText}</span>
|
|
383
546
|
{hasSelection ? (
|
|
384
547
|
<span
|
|
@@ -487,10 +650,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
487
650
|
</div>
|
|
488
651
|
<DialogDescription>
|
|
489
652
|
{hasSelection
|
|
490
|
-
?
|
|
491
|
-
'customers.ai_assistant.sheet.descriptionWithSelection',
|
|
492
|
-
'Working with {count} selected contacts. Ask about their details, deals, companies, and activities.',
|
|
493
|
-
).replace('{count}', String(selectedCount))
|
|
653
|
+
? viewCopy.descriptionWithSelection
|
|
494
654
|
: t(
|
|
495
655
|
'customers.ai_assistant.sheet.description',
|
|
496
656
|
'Your CRM assistant. Ask about people, companies, deals, and activities.',
|
|
@@ -504,6 +664,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
|
|
|
504
664
|
contextItems={contextItems}
|
|
505
665
|
hasSelection={hasSelection}
|
|
506
666
|
selectedCount={selectedCount}
|
|
667
|
+
viewCopy={viewCopy}
|
|
507
668
|
/>
|
|
508
669
|
</DialogContent>
|
|
509
670
|
</Dialog>
|
|
@@ -518,6 +679,7 @@ interface CustomersChatBodyProps {
|
|
|
518
679
|
contextItems: AiChatContextItem[]
|
|
519
680
|
hasSelection: boolean
|
|
520
681
|
selectedCount: number
|
|
682
|
+
viewCopy: ViewCopy
|
|
521
683
|
}
|
|
522
684
|
|
|
523
685
|
function CustomersChatBody({
|
|
@@ -527,6 +689,7 @@ function CustomersChatBody({
|
|
|
527
689
|
contextItems,
|
|
528
690
|
hasSelection,
|
|
529
691
|
selectedCount,
|
|
692
|
+
viewCopy,
|
|
530
693
|
}: CustomersChatBodyProps) {
|
|
531
694
|
const t = useT()
|
|
532
695
|
const sessions = useAiChatSessions()
|
|
@@ -563,14 +726,8 @@ function CustomersChatBody({
|
|
|
563
726
|
welcomeTitle={t('customers.ai_assistant.sheet.welcomeTitle', 'CRM Assistant')}
|
|
564
727
|
welcomeDescription={
|
|
565
728
|
hasSelection
|
|
566
|
-
?
|
|
567
|
-
|
|
568
|
-
'Ready to explore your {count} selected contacts:',
|
|
569
|
-
).replace('{count}', String(selectedCount))
|
|
570
|
-
: t(
|
|
571
|
-
'customers.ai_assistant.sheet.welcomeDescriptionAll',
|
|
572
|
-
'Ask me anything about your customers, companies, and deals:',
|
|
573
|
-
)
|
|
729
|
+
? viewCopy.welcomeDescriptionSelection
|
|
730
|
+
: viewCopy.welcomeDescriptionAll
|
|
574
731
|
}
|
|
575
732
|
/>
|
|
576
733
|
) : null}
|
|
@@ -5,28 +5,34 @@ import AiAssistantTriggerWidget from './widget.client'
|
|
|
5
5
|
* Step 4.10 — Backend AiChat injection example.
|
|
6
6
|
*
|
|
7
7
|
* Demonstrates how a third-party module can drop `<AiChat>` onto a page
|
|
8
|
-
* it does NOT own (the customers People
|
|
9
|
-
* injection system. Targets
|
|
10
|
-
*
|
|
8
|
+
* it does NOT own (the customers People / Companies / Deals lists) via
|
|
9
|
+
* the existing widget injection system. Targets DataTable header /
|
|
10
|
+
* search-trailing spots owned by the `DataTable` primitive in
|
|
11
|
+
* `packages/ui`.
|
|
11
12
|
*
|
|
12
13
|
* The trigger button opens a right-side sheet embedding
|
|
13
14
|
* `<AiChat agent="customers.account_assistant" pageContext={...} />`.
|
|
14
15
|
* `pageContext` follows the spec §10.1 shape:
|
|
15
16
|
*
|
|
16
|
-
* { view: 'customers.people.list',
|
|
17
|
+
* { view: 'customers.people.list' | 'customers.companies.list' | 'customers.deals.list',
|
|
17
18
|
* recordType: null,
|
|
18
19
|
* recordId: string, // "" or comma-separated UUIDs
|
|
19
20
|
* extra: { selectedCount, totalMatching } }
|
|
20
21
|
*
|
|
21
|
-
* Feature-gated behind `
|
|
22
|
+
* Feature-gated behind `ai_assistant.view` only — the host CRM page
|
|
23
|
+
* already enforces its own `customers.people.view`/`companies.view`/
|
|
24
|
+
* `deals.view` guard, so the widget gate just needs to ensure the user
|
|
25
|
+
* has AI assistant access. (Earlier versions also listed
|
|
26
|
+
* `customers.people.view`, which is AND-evaluated by the registry and
|
|
27
|
+
* hid the trigger from companies-only or deals-only viewers.)
|
|
22
28
|
*/
|
|
23
29
|
const widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {
|
|
24
30
|
metadata: {
|
|
25
31
|
id: 'customers.injection.ai-assistant-trigger',
|
|
26
32
|
title: 'Customers AI Assistant Trigger',
|
|
27
33
|
description:
|
|
28
|
-
'Renders an "Ask AI" button in the people list
|
|
29
|
-
features: ['
|
|
34
|
+
'Renders an "Ask AI" button in the people / companies / deals list headers that opens a sheet embedding the customers account assistant.',
|
|
35
|
+
features: ['ai_assistant.view'],
|
|
30
36
|
requiredModules: ['ai_assistant'],
|
|
31
37
|
priority: 100,
|
|
32
38
|
enabled: true,
|
|
@@ -30,6 +30,12 @@ export const injectionTable: ModuleInjectionTable = {
|
|
|
30
30
|
priority: 100,
|
|
31
31
|
},
|
|
32
32
|
],
|
|
33
|
+
'data-table:customers.deals.list:search-trailing': [
|
|
34
|
+
{
|
|
35
|
+
widgetId: 'customers.injection.ai-assistant-trigger',
|
|
36
|
+
priority: 100,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
33
39
|
'detail:customers.deal:header': [
|
|
34
40
|
{
|
|
35
41
|
widgetId: 'customers.injection.ai-deal-detail-trigger',
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from '@open-mercato/ui/primitives/select'
|
|
21
21
|
import { Switch } from '@open-mercato/ui/primitives/switch'
|
|
22
22
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
23
|
+
import { PasswordInput } from '@open-mercato/ui/primitives/password-input'
|
|
23
24
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
24
25
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
|
|
25
26
|
import { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'
|
|
@@ -212,9 +213,8 @@ function buildCredentialFields(credFields: CredentialField[]): CrudField[] {
|
|
|
212
213
|
...shared,
|
|
213
214
|
type: 'custom' as const,
|
|
214
215
|
component: ({ id, value, setValue, disabled }) => (
|
|
215
|
-
<
|
|
216
|
+
<PasswordInput
|
|
216
217
|
id={id}
|
|
217
|
-
type="password"
|
|
218
218
|
placeholder={field.placeholder}
|
|
219
219
|
value={typeof value === 'string' ? value : ''}
|
|
220
220
|
onChange={(event) => setValue(event.target.value)}
|
|
@@ -95,26 +95,54 @@ function isPerspectivesState(value: unknown): value is PerspectivesState {
|
|
|
95
95
|
return true
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Defensive migration for legacy filter state shapes captured before the
|
|
100
|
+
* advanced-filter tree (SPEC-048). Existing perspectives only store either
|
|
101
|
+
* advanced-filter URL params (tree shape with `v:2` or a `root` key) or
|
|
102
|
+
* undefined — this helper is a safety net for legacy `FilterValues`-shaped
|
|
103
|
+
* records (flat key/value records of column filters) that could only appear
|
|
104
|
+
* if old saved-view JSON were imported.
|
|
105
|
+
*
|
|
106
|
+
* - Tree-shaped state (`v:2` or `root` key) is passed through unchanged.
|
|
107
|
+
* - Undefined / null filters are passed through unchanged.
|
|
108
|
+
* - Legacy `FilterValues`-shaped records are dropped (set to `undefined`)
|
|
109
|
+
* because there is no reliable mapping back to the new operator model;
|
|
110
|
+
* the user sees an empty tree and can recreate.
|
|
111
|
+
*/
|
|
112
|
+
export function maybeMigrateLegacyFilterValues(settings: PerspectiveSettings): PerspectiveSettings {
|
|
113
|
+
const filters = settings.filters
|
|
114
|
+
if (!filters || typeof filters !== 'object') return settings
|
|
115
|
+
const record = filters as Record<string, unknown>
|
|
116
|
+
if ('v' in record && record.v === 2) return settings
|
|
117
|
+
if ('root' in record) return settings
|
|
118
|
+
if (typeof console !== 'undefined') {
|
|
119
|
+
console.warn('[perspectives] Dropping legacy filterValues shape; please re-create the perspective with the new filter UI.')
|
|
120
|
+
}
|
|
121
|
+
return { ...settings, filters: undefined }
|
|
122
|
+
}
|
|
123
|
+
|
|
98
124
|
function toResolvedPerspective(entity: Perspective): ResolvedPerspective {
|
|
125
|
+
const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)
|
|
99
126
|
return {
|
|
100
127
|
id: entity.id,
|
|
101
128
|
name: entity.name,
|
|
102
129
|
tableId: entity.tableId,
|
|
103
130
|
isDefault: !!entity.isDefault,
|
|
104
|
-
settings
|
|
131
|
+
settings,
|
|
105
132
|
createdAt: entity.createdAt.toISOString(),
|
|
106
133
|
updatedAt: entity.updatedAt ? entity.updatedAt.toISOString() : null,
|
|
107
134
|
}
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
function toResolvedRolePerspective(entity: RolePerspective): ResolvedRolePerspective {
|
|
138
|
+
const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)
|
|
111
139
|
return {
|
|
112
140
|
id: entity.id,
|
|
113
141
|
roleId: entity.roleId,
|
|
114
142
|
tableId: entity.tableId,
|
|
115
143
|
name: entity.name,
|
|
116
144
|
isDefault: !!entity.isDefault,
|
|
117
|
-
settings
|
|
145
|
+
settings,
|
|
118
146
|
tenantId: nullish(entity.tenantId),
|
|
119
147
|
organizationId: nullish(entity.organizationId),
|
|
120
148
|
createdAt: entity.createdAt.toISOString(),
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { useCallback, useMemo, useState } from 'react'
|
|
3
3
|
import Link from 'next/link'
|
|
4
4
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
|
-
import {
|
|
5
|
+
import { EmailInput } from '@open-mercato/ui/primitives/email-input'
|
|
6
|
+
import { PasswordInput } from '@open-mercato/ui/primitives/password-input'
|
|
6
7
|
import { Label } from '@open-mercato/ui/primitives/label'
|
|
7
8
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
8
9
|
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
@@ -39,6 +40,7 @@ export default function PortalLoginPage({ params }: Props) {
|
|
|
39
40
|
const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/login', {
|
|
40
41
|
method: 'POST',
|
|
41
42
|
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
credentials: 'include',
|
|
42
44
|
body: JSON.stringify({ email, password, tenantId: tenant.tenantId }),
|
|
43
45
|
})
|
|
44
46
|
|
|
@@ -107,12 +109,12 @@ export default function PortalLoginPage({ params }: Props) {
|
|
|
107
109
|
|
|
108
110
|
<div className="flex flex-col gap-1.5">
|
|
109
111
|
<Label htmlFor="login-email" className="text-overline font-semibold uppercase tracking-wider text-muted-foreground/70">{t('portal.login.email', 'Email')}</Label>
|
|
110
|
-
<
|
|
112
|
+
<EmailInput id="login-email" required placeholder={t('portal.login.email.placeholder', 'you@example.com')} value={email} onChange={(e) => setEmail(e.target.value)} disabled={submitting} className="rounded-lg" />
|
|
111
113
|
</div>
|
|
112
114
|
|
|
113
115
|
<div className="flex flex-col gap-1.5">
|
|
114
116
|
<Label htmlFor="login-password" className="text-overline font-semibold uppercase tracking-wider text-muted-foreground/70">{t('portal.login.password', 'Password')}</Label>
|
|
115
|
-
<
|
|
117
|
+
<PasswordInput id="login-password" autoComplete="current-password" required placeholder={t('portal.login.password.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting} className="rounded-lg" />
|
|
116
118
|
</div>
|
|
117
119
|
|
|
118
120
|
<Button type="submit" disabled={submitting} className="mt-1 w-full rounded-lg">
|
|
@@ -3,7 +3,7 @@ import { useCallback, useMemo, useState, useEffect } from 'react'
|
|
|
3
3
|
import { useSearchParams } from 'next/navigation'
|
|
4
4
|
import Link from 'next/link'
|
|
5
5
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
|
-
import {
|
|
6
|
+
import { PasswordInput } from '@open-mercato/ui/primitives/password-input'
|
|
7
7
|
import { Label } from '@open-mercato/ui/primitives/label'
|
|
8
8
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
9
|
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
@@ -142,12 +142,12 @@ export default function PortalResetPasswordPage({ params }: Props) {
|
|
|
142
142
|
|
|
143
143
|
<div className="flex flex-col gap-1.5">
|
|
144
144
|
<Label htmlFor="reset-password" className="text-overline font-semibold uppercase tracking-wider text-muted-foreground/70">{t('portal.resetPassword.password', 'New Password')}</Label>
|
|
145
|
-
<
|
|
145
|
+
<PasswordInput id="reset-password" autoComplete="new-password" required placeholder={t('portal.resetPassword.password.placeholder', '••••••••')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting || !token} className="rounded-lg" />
|
|
146
146
|
</div>
|
|
147
147
|
|
|
148
148
|
<div className="flex flex-col gap-1.5">
|
|
149
149
|
<Label htmlFor="confirm-password" className="text-overline font-semibold uppercase tracking-wider text-muted-foreground/70">{t('portal.resetPassword.confirmPassword', 'Confirm New Password')}</Label>
|
|
150
|
-
<
|
|
150
|
+
<PasswordInput id="confirm-password" autoComplete="new-password" required placeholder={t('portal.resetPassword.confirmPassword.placeholder', '••••••••')} value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={submitting || !token} className="rounded-lg" />
|
|
151
151
|
</div>
|
|
152
152
|
|
|
153
153
|
<Button type="submit" disabled={submitting || !token} className="mt-1 w-full rounded-lg">
|
|
@@ -3,6 +3,8 @@ import { useCallback, useMemo, useState } from 'react'
|
|
|
3
3
|
import Link from 'next/link'
|
|
4
4
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
5
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
6
|
+
import { EmailInput } from '@open-mercato/ui/primitives/email-input'
|
|
7
|
+
import { PasswordInput } from '@open-mercato/ui/primitives/password-input'
|
|
6
8
|
import { Label } from '@open-mercato/ui/primitives/label'
|
|
7
9
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
8
10
|
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
@@ -122,12 +124,12 @@ export default function PortalSignupPage({ params }: Props) {
|
|
|
122
124
|
|
|
123
125
|
<div className="flex flex-col gap-1.5">
|
|
124
126
|
<Label htmlFor="signup-email" className="text-overline font-semibold uppercase tracking-wider text-muted-foreground/70">{t('portal.signup.email', 'Email')}</Label>
|
|
125
|
-
<
|
|
127
|
+
<EmailInput id="signup-email" required placeholder={t('portal.signup.email.placeholder', 'you@example.com')} value={email} onChange={(e) => setEmail(e.target.value)} disabled={submitting} className="rounded-lg" />
|
|
126
128
|
</div>
|
|
127
129
|
|
|
128
130
|
<div className="flex flex-col gap-1.5">
|
|
129
131
|
<Label htmlFor="signup-password" className="text-overline font-semibold uppercase tracking-wider text-muted-foreground/70">{t('portal.signup.password', 'Password')}</Label>
|
|
130
|
-
<
|
|
132
|
+
<PasswordInput id="signup-password" autoComplete="new-password" required placeholder={t('portal.signup.password.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting} className="rounded-lg" />
|
|
131
133
|
</div>
|
|
132
134
|
|
|
133
135
|
<Button type="submit" disabled={submitting} className="mt-1 w-full rounded-lg">
|
|
@@ -625,14 +625,19 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
625
625
|
existing.push(filter)
|
|
626
626
|
groups.set(filter.orGroup, existing)
|
|
627
627
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
628
|
+
const groupList = Array.from(groups.values()).filter((g) => g.length > 0)
|
|
629
|
+
if (groupList.length === 0) return q
|
|
630
|
+
// Combine all groups in a single WHERE: disjuncts are OR'd together; within
|
|
631
|
+
// each disjunct, fields are AND'd. Building this as separate `.where()` calls
|
|
632
|
+
// would AND the disjuncts (wrong semantics).
|
|
633
|
+
return q.where((eb: any) => eb.or(
|
|
634
|
+
groupList.map((groupFilters) => {
|
|
635
|
+
const parts = groupFilters.map((filter) =>
|
|
636
|
+
this.buildBaseFilterExpression(eb, filter, resolveBaseColumn, qualify, entity, searchRuntime),
|
|
637
|
+
)
|
|
638
|
+
return parts.length === 1 ? parts[0] : eb.and(parts)
|
|
639
|
+
}),
|
|
640
|
+
))
|
|
636
641
|
}
|
|
637
642
|
|
|
638
643
|
const applyAliasScopes = async (target: AnyBuilder, aliasName: string): Promise<AnyBuilder> => {
|
|
@@ -1341,6 +1346,39 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1341
1346
|
// Doc-based filter via `ei` alias — returned as EXISTS where possible
|
|
1342
1347
|
return this.buildIndexDocFilterExpression(eb, 'ei', entity, fieldName, filter.op, filter.value, 'b.id', searchRuntime)
|
|
1343
1348
|
}
|
|
1349
|
+
// For like/ilike with active search-tokens, route through hashed-token EXISTS subquery
|
|
1350
|
+
// so encrypted-at-rest columns can still be searched.
|
|
1351
|
+
if (
|
|
1352
|
+
(filter.op === 'like' || filter.op === 'ilike') &&
|
|
1353
|
+
searchRuntime?.enabled &&
|
|
1354
|
+
typeof filter.value === 'string'
|
|
1355
|
+
) {
|
|
1356
|
+
const tokens = tokenizeText(String(filter.value), searchRuntime.config)
|
|
1357
|
+
if (tokens.hashes.length) {
|
|
1358
|
+
const sources: SearchTokenSource[] = (searchRuntime.searchSources && searchRuntime.searchSources.length
|
|
1359
|
+
? searchRuntime.searchSources
|
|
1360
|
+
: [{ entity: String(entity), recordIdColumn: 'b.id' }]
|
|
1361
|
+
).filter((src) => src.recordIdColumn && src.entity)
|
|
1362
|
+
if (sources.length) {
|
|
1363
|
+
return eb.or(
|
|
1364
|
+
sources.map((src) =>
|
|
1365
|
+
eb.exists(this.buildSearchTokensSub(eb, {
|
|
1366
|
+
entity: src.entity,
|
|
1367
|
+
field: fieldName,
|
|
1368
|
+
hashes: tokens.hashes,
|
|
1369
|
+
recordIdColumn: src.recordIdColumn,
|
|
1370
|
+
tenantId: searchRuntime.tenantId ?? null,
|
|
1371
|
+
organizationScope: searchRuntime.organizationScope ?? null,
|
|
1372
|
+
})),
|
|
1373
|
+
),
|
|
1374
|
+
)
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
// Tokenizer produced no hashes (e.g. value too short). Match the regular-base-filter
|
|
1378
|
+
// path's behavior of skipping the predicate (no filter), which is preferable to
|
|
1379
|
+
// silently turning into a plain `ilike` against an encrypted column.
|
|
1380
|
+
return sql<boolean>`true`
|
|
1381
|
+
}
|
|
1344
1382
|
return this.buildColumnFilterExpression(eb, qualify(baseField), filter.op, filter.value)
|
|
1345
1383
|
}
|
|
1346
1384
|
|
|
@@ -20,6 +20,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
|
|
|
20
20
|
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
21
21
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
22
22
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
23
|
+
import { EmailInput } from '@open-mercato/ui/primitives/email-input'
|
|
23
24
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
|
|
24
25
|
import { ArrowRightLeft, Building2, CreditCard, Mail, Pencil, Plus, Send, Store, Truck, UserRound, Wand2, X } from 'lucide-react'
|
|
25
26
|
import { FormHeader, type ActionItem } from '@open-mercato/ui/backend/forms'
|
|
@@ -663,8 +664,7 @@ function CustomerInlineEditor({
|
|
|
663
664
|
<label className="text-sm font-medium text-foreground">
|
|
664
665
|
{t('customers.people.detail.highlights.primaryEmail', 'Primary email')}
|
|
665
666
|
</label>
|
|
666
|
-
<
|
|
667
|
-
type="email"
|
|
667
|
+
<EmailInput
|
|
668
668
|
value={snapshotDraft.primaryEmail}
|
|
669
669
|
onChange={(event) => setSnapshotDraft((prev) => ({ ...prev, primaryEmail: event.target.value }))}
|
|
670
670
|
placeholder={t('customers.people.form.primaryEmailPlaceholder', 'name@example.com')}
|
|
@@ -1139,15 +1139,13 @@ function ContactEmailInlineEditor({
|
|
|
1139
1139
|
}
|
|
1140
1140
|
}}
|
|
1141
1141
|
>
|
|
1142
|
-
<
|
|
1143
|
-
leftIcon={<Mail />}
|
|
1142
|
+
<EmailInput
|
|
1144
1143
|
value={draft}
|
|
1145
1144
|
onChange={(event) => {
|
|
1146
1145
|
if (error) setError(null)
|
|
1147
1146
|
setDraft(event.target.value)
|
|
1148
1147
|
}}
|
|
1149
1148
|
placeholder={placeholder}
|
|
1150
|
-
type="email"
|
|
1151
1149
|
autoFocus
|
|
1152
1150
|
spellCheck={false}
|
|
1153
1151
|
/>
|