@open-mercato/core 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7294.1.0ef99fc518
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 +1 -1
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/components/AclEditor.js +21 -3
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +6 -4
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +6 -4
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
- package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/index.js +3 -1
- package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +17 -9
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/data/validators.js +12 -7
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/lib/assignableStaff.js +103 -22
- package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
- package/dist/modules/customers/setup.js +10 -2
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +13 -2
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/schedule-value.js +30 -0
- package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
- package/dist/modules/portal/lib/navigation.js +3 -4
- package/dist/modules/portal/lib/navigation.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +79 -14
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/extension-points.js +16 -0
- package/dist/modules/staff/extension-points.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +3 -3
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +4 -4
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
- package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +3 -3
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +3 -3
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
- package/dist/modules/warranty_claims/backend/page.js +4 -2
- package/dist/modules/warranty_claims/backend/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
- package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/users/acl/route.ts +4 -3
- package/src/modules/auth/components/AclEditor.tsx +38 -2
- package/src/modules/auth/i18n/de.json +5 -1
- package/src/modules/auth/i18n/en.json +5 -1
- package/src/modules/auth/i18n/es.json +5 -1
- package/src/modules/auth/i18n/ko.json +5 -1
- package/src/modules/auth/i18n/pl.json +5 -1
- package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
- package/src/modules/customers/backend/customers/people/page.tsx +6 -4
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
- package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
- package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
- package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
- package/src/modules/customers/components/detail/schedule/index.ts +1 -1
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
- package/src/modules/customers/components/formConfig.tsx +35 -12
- package/src/modules/customers/data/validators.ts +14 -8
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/ko.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/customers/lib/assignableStaff.ts +172 -34
- package/src/modules/customers/setup.ts +8 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
- package/src/modules/data_sync/api/schedules/route.ts +5 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
- package/src/modules/data_sync/data/validators.ts +26 -2
- package/src/modules/data_sync/i18n/de.json +2 -0
- package/src/modules/data_sync/i18n/en.json +2 -0
- package/src/modules/data_sync/i18n/es.json +2 -0
- package/src/modules/data_sync/i18n/ko.json +2 -0
- package/src/modules/data_sync/i18n/pl.json +2 -0
- package/src/modules/data_sync/lib/schedule-value.ts +45 -0
- package/src/modules/eudr/data/validators.ts +3 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
- package/src/modules/portal/i18n/de.json +1 -0
- package/src/modules/portal/i18n/en.json +1 -0
- package/src/modules/portal/i18n/es.json +1 -0
- package/src/modules/portal/i18n/ko.json +1 -0
- package/src/modules/portal/i18n/pl.json +1 -0
- package/src/modules/portal/lib/navigation.ts +8 -3
- package/src/modules/query_index/lib/engine.ts +111 -16
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
- package/src/modules/staff/extension-points.ts +16 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
- package/src/modules/staff/i18n/de.json +3 -0
- package/src/modules/staff/i18n/en.json +3 -0
- package/src/modules/staff/i18n/es.json +3 -0
- package/src/modules/staff/i18n/ko.json +3 -0
- package/src/modules/staff/i18n/pl.json +3 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
- package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
- package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
- package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
- package/src/modules/warranty_claims/api/assign/route.ts +3 -3
- package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
- package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
- package/src/modules/warranty_claims/api/events/route.ts +4 -4
- package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
- package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
- package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
- package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
- package/src/modules/warranty_claims/api/risk/route.ts +2 -2
- package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
- package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
- package/src/modules/warranty_claims/api/stats/route.ts +2 -2
- package/src/modules/warranty_claims/api/submit/route.ts +3 -3
- package/src/modules/warranty_claims/api/transition/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
- package/src/modules/warranty_claims/backend/page.tsx +4 -2
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
- package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
- package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
- package/src/modules/workflows/i18n/de.json +1 -1
- package/src/modules/workflows/i18n/en.json +1 -1
- package/src/modules/workflows/i18n/es.json +1 -1
- package/src/modules/workflows/i18n/ko.json +1 -1
- package/src/modules/workflows/i18n/pl.json +1 -1
- package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/invite/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState, useEffect } from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { PortalInjectionSpots } from '@open-mercato/ui/backend/injection/spotIds'\nimport { navigateWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalInvitePage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n const searchParams = useSearchParams()\n\n const [displayName, setDisplayName] = useState('')\n const [password, setPassword] = useState('')\n const [confirmPassword, setConfirmPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [token, setToken] = useState<string | null>(null)\n\n useEffect(() => {\n const tokenParam = searchParams.get('token')\n if (!tokenParam) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n } else {\n setToken(tokenParam)\n }\n }, [searchParams, t])\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!token) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n return\n }\n\n if (!displayName.trim()) {\n setError(t('portal.invite.error.displayNameRequired', 'Display name is required.'))\n return\n }\n\n if (password !== confirmPassword) {\n setError(t('portal.invite.error.passwordMismatch', 'Passwords do not match.'))\n return\n }\n\n if (password.length < 8) {\n setError(t('portal.invite.error.passwordTooShort', 'Password must be at least 8 characters long.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/invitations/accept', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token, password, displayName: displayName.trim() }),\n })\n\n if (result.ok && result.result?.ok) {\n navigateWithPageReload(`/${orgSlug}/portal/dashboard`)\n return\n }\n\n if (result.status === 400) {\n setError(t('portal.invite.error.invalidToken', 'Invalid or expired invitation.'))\n } else {\n setError(result.result?.error || t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n }\n } catch {\n setError(t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n },\n [token, password, confirmPassword, displayName, orgSlug, t],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto w-full max-w-sm\">\n <div className=\"mb-8 text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{t('portal.invite.title', 'Accept Invitation')}</h1>\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{t('portal.invite.description', 'Create your portal account to accept this invitation.')}</p>\n </div>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageBefore('invite')} context={injectionContext} />\n\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n {error ? (\n <Alert status=\"error\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-display-name\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.displayName', 'Display Name')}</Label>\n <Input id=\"invite-display-name\" autoComplete=\"name\" required value={displayName} onChange={(e) => setDisplayName(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.password', 'Password')}</Label>\n <PasswordInput id=\"invite-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.password.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-confirm-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.confirmPassword', 'Confirm Password')}</Label>\n <PasswordInput id=\"invite-confirm-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.confirmPassword.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <Button type=\"submit\" disabled={submitting || !token} className=\"mt-1 w-full rounded-lg\">\n {submitting ? t('portal.invite.submitting', 'Accepting invitation...') : t('portal.invite.submit', 'Accept Invitation')}\n </Button>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t('portal.invite.backToLogin', 'Already have an account?')}{' '}\n <Link href={`/${orgSlug}/portal/login`} className=\"font-medium text-foreground underline underline-offset-4 hover:opacity-80\">\n {t('portal.invite.loginLink', 'Sign in')}\n </Link>\n </p>\n </form>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageAfter('invite')} context={injectionContext} />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState, useEffect } from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { PortalInjectionSpots } from '@open-mercato/ui/backend/injection/spotIds'\nimport { navigateWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalInvitePage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n const searchParams = useSearchParams()\n\n const [displayName, setDisplayName] = useState('')\n const [password, setPassword] = useState('')\n const [confirmPassword, setConfirmPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [token, setToken] = useState<string | null>(null)\n\n useEffect(() => {\n const tokenParam = searchParams.get('token')\n if (!tokenParam) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n } else {\n setToken(tokenParam)\n }\n }, [searchParams, t])\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!token) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n return\n }\n\n if (!displayName.trim()) {\n setError(t('portal.invite.error.displayNameRequired', 'Display name is required.'))\n return\n }\n\n if (password !== confirmPassword) {\n setError(t('portal.invite.error.passwordMismatch', 'Passwords do not match.'))\n return\n }\n\n if (password.length < 8) {\n setError(t('portal.invite.error.passwordTooShort', 'Password must be at least 8 characters long.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/invitations/accept', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token, password, displayName: displayName.trim() }),\n })\n\n if (result.ok && result.result?.ok) {\n navigateWithPageReload(`/${orgSlug}/portal/dashboard`)\n return\n }\n\n if (result.status === 409) {\n setError(t('portal.invite.error.accountExists', 'This email address already has a portal account. Sign in below instead of accepting the invitation.'))\n } else if (result.status === 400) {\n setError(t('portal.invite.error.invalidToken', 'Invalid or expired invitation.'))\n } else {\n setError(result.result?.error || t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n }\n } catch {\n setError(t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n },\n [token, password, confirmPassword, displayName, orgSlug, t],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto w-full max-w-sm\">\n <div className=\"mb-8 text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{t('portal.invite.title', 'Accept Invitation')}</h1>\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{t('portal.invite.description', 'Create your portal account to accept this invitation.')}</p>\n </div>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageBefore('invite')} context={injectionContext} />\n\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n {error ? (\n <Alert status=\"error\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-display-name\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.displayName', 'Display Name')}</Label>\n <Input id=\"invite-display-name\" autoComplete=\"name\" required value={displayName} onChange={(e) => setDisplayName(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.password', 'Password')}</Label>\n <PasswordInput id=\"invite-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.password.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-confirm-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.confirmPassword', 'Confirm Password')}</Label>\n <PasswordInput id=\"invite-confirm-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.confirmPassword.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <Button type=\"submit\" disabled={submitting || !token} className=\"mt-1 w-full rounded-lg\">\n {submitting ? t('portal.invite.submitting', 'Accepting invitation...') : t('portal.invite.submit', 'Accept Invitation')}\n </Button>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t('portal.invite.backToLogin', 'Already have an account?')}{' '}\n <Link href={`/${orgSlug}/portal/login`} className=\"font-medium text-foreground underline underline-offset-4 hover:opacity-80\">\n {t('portal.invite.loginLink', 'Sign in')}\n </Link>\n </p>\n </form>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageAfter('invite')} context={injectionContext} />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
|
|
|
13
13
|
import { SearchX } from "lucide-react";
|
|
14
14
|
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
15
15
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
16
|
+
import { navigateWithPageReload } from "@open-mercato/core/modules/portal/lib/navigation";
|
|
16
17
|
import { usePortalContext } from "@open-mercato/ui/portal/PortalContext";
|
|
17
18
|
import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
|
|
18
19
|
function PortalLoginPage({ params }) {
|
|
@@ -40,7 +41,7 @@ function PortalLoginPage({ params }) {
|
|
|
40
41
|
body: JSON.stringify({ email, password, organizationId: tenant.organizationId })
|
|
41
42
|
});
|
|
42
43
|
if (result.ok && result.result?.ok) {
|
|
43
|
-
|
|
44
|
+
navigateWithPageReload(`/${orgSlug}/portal/dashboard`);
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
47
|
if (result.status === 401) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/login/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalLoginPage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!tenant.organizationId) {\n setError(t('portal.org.invalid', 'Organization not found.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ email, password, organizationId: tenant.organizationId }),\n })\n\n if (result.ok && result.result?.ok) {\n
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { navigateWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalLoginPage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!tenant.organizationId) {\n setError(t('portal.org.invalid', 'Organization not found.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ email, password, organizationId: tenant.organizationId }),\n })\n\n if (result.ok && result.result?.ok) {\n navigateWithPageReload(`/${orgSlug}/portal/dashboard`)\n return\n }\n\n if (result.status === 401) {\n // The login API intentionally returns a single generic 401 for unknown, inactive,\n // locked, and unverified accounts so account existence cannot be enumerated.\n // Lockout/deactivation guidance is delivered out-of-band (e.g. email), never here.\n setError(t('portal.login.error.invalidCredentials', 'Invalid email or password.'))\n } else {\n setError(result.result?.error || t('portal.login.error.generic', 'Login failed. Please try again.'))\n }\n } catch {\n setError(t('portal.login.error.generic', 'Login failed. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n },\n [email, password, tenant.organizationId, orgSlug, t],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto w-full max-w-sm\">\n <div className=\"mb-8 text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{t('portal.login.title', 'Sign In')}</h1>\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{t('portal.login.description', 'Enter your credentials to access the portal.')}</p>\n </div>\n\n <InjectionSpot spotId={extensionPoints.hosts.loginBefore.spotId} context={injectionContext} />\n\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n {error ? (\n <Alert status=\"error\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"login-email\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.login.email', 'Email')}</Label>\n <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\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"login-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.login.password', 'Password')}</Label>\n <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\" />\n </div>\n\n <Button type=\"submit\" disabled={submitting} className=\"mt-1 w-full rounded-lg\">\n {submitting ? t('portal.login.submitting', 'Signing in...') : t('portal.login.submit', 'Sign In')}\n </Button>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t('portal.login.noAccount', \"Don't have an account?\")}{' '}\n <Link href={`/${orgSlug}/portal/signup`} className=\"font-medium text-foreground underline underline-offset-4 hover:opacity-80\">\n {t('portal.login.signupLink', 'Sign up')}\n </Link>\n </p>\n </form>\n\n <InjectionSpot spotId={extensionPoints.hosts.loginAfter.spotId} context={injectionContext} />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useMemo } from "react";
|
|
4
4
|
import { extensionPoints } from "@open-mercato/core/modules/portal/extension-points";
|
|
5
5
|
import Link from "next/link";
|
|
6
|
-
import {
|
|
6
|
+
import { replaceWithPageReload } from "@open-mercato/core/modules/portal/lib/navigation";
|
|
7
7
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
8
8
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
9
|
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
@@ -14,14 +14,13 @@ import { PortalFeatureCard } from "@open-mercato/ui/portal/components/PortalFeat
|
|
|
14
14
|
import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
|
|
15
15
|
function PortalLandingPage({ params }) {
|
|
16
16
|
const t = useT();
|
|
17
|
-
const router = useRouter();
|
|
18
17
|
const orgSlug = params.orgSlug;
|
|
19
18
|
const { auth, tenant } = usePortalContext();
|
|
20
19
|
useEffect(() => {
|
|
21
20
|
if (!auth.loading && auth.user) {
|
|
22
|
-
|
|
21
|
+
replaceWithPageReload(`/${orgSlug}/portal/dashboard`);
|
|
23
22
|
}
|
|
24
|
-
}, [auth.loading, auth.user,
|
|
23
|
+
}, [auth.loading, auth.user, orgSlug]);
|
|
25
24
|
const injectionContext = useMemo(
|
|
26
25
|
() => ({ orgSlug }),
|
|
27
26
|
[orgSlug]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport {
|
|
5
|
-
"mappings": ";AAmCmE,SAoB/D,UApB+D,KAiC3D,YAjC2D;AAlCnE,SAAS,WAAW,eAAe;AACnC,SAAS,uBAAuB;AAChC,OAAO,UAAU;AACjB,SAAS,
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX, ShoppingBag, User, Shield } from 'lucide-react'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalFeatureCard } from '@open-mercato/ui/portal/components/PortalFeatureCard'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalLandingPage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { auth, tenant } = usePortalContext()\n\n // Redirect authenticated users to dashboard. This crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n useEffect(() => {\n if (!auth.loading && auth.user) {\n replaceWithPageReload(`/${orgSlug}/portal/dashboard`)\n }\n }, [auth.loading, auth.user, orgSlug])\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (auth.loading || tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n // Authenticated user \u2014 redirect is in progress\n if (auth.user) return null\n\n return (\n <>\n <InjectionSpot spotId={extensionPoints.hosts.homeBefore.spotId} context={injectionContext} />\n\n <section className=\"flex flex-col items-center gap-5 py-8 text-center sm:py-16\">\n <p className=\"text-overline font-semibold uppercase tracking-widest text-muted-foreground/60\">\n {t('portal.nav.home', 'Customer Portal')}\n </p>\n <h1 className=\"max-w-2xl text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl\">\n {t('portal.landing.hero.title', 'Welcome to your portal')}\n </h1>\n <p className=\"max-w-lg text-base leading-relaxed text-muted-foreground sm:text-lg\">\n {t('portal.landing.hero.description', 'Access your account, manage orders, and stay up to date.')}\n </p>\n <div className=\"mt-2 flex items-center gap-3\">\n <Button asChild size=\"lg\" className=\"rounded-lg px-6 text-sm\">\n <Link href={`/${orgSlug}/portal/login`}>{t('portal.landing.cta.login', 'Sign In')}</Link>\n </Button>\n <Button asChild variant=\"outline\" size=\"lg\" className=\"rounded-lg px-6 text-sm\">\n <Link href={`/${orgSlug}/portal/signup`}>{t('portal.landing.cta.signup', 'Create Account')}</Link>\n </Button>\n </div>\n </section>\n\n <section className=\"grid gap-4 sm:grid-cols-3\">\n <PortalFeatureCard\n icon={<ShoppingBag className=\"size-5\" />}\n title={t('portal.landing.feature.orders', 'Orders & Invoices')}\n description={t('portal.landing.feature.orders.description', 'Track your orders, download invoices, and view delivery status in real time.')}\n />\n <PortalFeatureCard\n icon={<User className=\"size-5\" />}\n title={t('portal.landing.feature.account', 'Account Management')}\n description={t('portal.landing.feature.account.description', 'Update your profile, manage team members, and configure your preferences.')}\n />\n <PortalFeatureCard\n icon={<Shield className=\"size-5\" />}\n title={t('portal.landing.feature.security', 'Secure Access')}\n description={t('portal.landing.feature.security.description', 'Role-based permissions, session management, and full audit trail.')}\n />\n </section>\n\n <InjectionSpot spotId={extensionPoints.hosts.homeAfter.spotId} context={injectionContext} />\n </>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo } from "react";
|
|
4
4
|
import { extensionPoints } from "@open-mercato/core/modules/portal/extension-points";
|
|
5
|
-
import {
|
|
5
|
+
import { replaceWithPageReload } from "@open-mercato/core/modules/portal/lib/navigation";
|
|
6
6
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
7
|
import { Badge } from "@open-mercato/ui/primitives/badge";
|
|
8
8
|
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
@@ -12,14 +12,13 @@ import { PortalCard, PortalCardHeader, PortalStatRow, PortalCardDivider } from "
|
|
|
12
12
|
import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
|
|
13
13
|
function PortalProfilePage({ params }) {
|
|
14
14
|
const t = useT();
|
|
15
|
-
const router = useRouter();
|
|
16
15
|
const { auth } = usePortalContext();
|
|
17
16
|
const { user, roles, resolvedFeatures, isPortalAdmin, loading } = auth;
|
|
18
17
|
useEffect(() => {
|
|
19
18
|
if (!loading && !user) {
|
|
20
|
-
|
|
19
|
+
replaceWithPageReload(`/${params.orgSlug}/portal/login`);
|
|
21
20
|
}
|
|
22
|
-
}, [loading, user,
|
|
21
|
+
}, [loading, user, params.orgSlug]);
|
|
23
22
|
const injectionContext = useMemo(
|
|
24
23
|
() => ({ orgSlug: params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin }),
|
|
25
24
|
[params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/profile/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport {
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'\nimport { PortalCard, PortalCardHeader, PortalStatRow, PortalCardDivider } from '@open-mercato/ui/portal/components/PortalCard'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalProfilePage({ params }: Props) {\n const t = useT()\n const { auth } = usePortalContext()\n const { user, roles, resolvedFeatures, isPortalAdmin, loading } = auth\n\n // Leaving an authenticated page for the login page crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n useEffect(() => {\n if (!loading && !user) {\n replaceWithPageReload(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, params.orgSlug])\n\n const injectionContext = useMemo(\n () => ({ orgSlug: params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin }),\n [params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin],\n )\n\n if (loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (!user) return null\n\n const formatDate = (dateString: string | null) => {\n if (!dateString) return t('portal.dashboard.never', 'Never')\n return new Date(dateString).toLocaleDateString(undefined, {\n year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',\n })\n }\n\n return (\n <div className=\"flex flex-col gap-8\">\n <PortalPageHeader\n label={t('portal.profile.label', 'Account')}\n title={t('portal.profile.title', 'Profile')}\n />\n\n <InjectionSpot spotId={extensionPoints.hosts.profileBefore.spotId} context={injectionContext} />\n\n <div className=\"grid gap-5 md:grid-cols-2\">\n <PortalCard>\n <PortalCardHeader label={t('portal.dashboard.profile', 'Profile')} title={user.displayName} />\n <div className=\"flex flex-col\">\n <PortalStatRow\n label={t('portal.dashboard.email', 'Email')}\n value={\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate\">{user.email}</span>\n <Badge variant={user.emailVerified ? 'default' : 'outline'} className=\"shrink-0 text-overline\">\n {user.emailVerified ? t('portal.dashboard.emailVerified', 'Verified') : t('portal.dashboard.emailNotVerified', 'Unverified')}\n </Badge>\n </span>\n }\n />\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.lastLogin', 'Last login')} value={formatDate(user.lastLoginAt)} />\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.memberSince', 'Member since')} value={formatDate(user.createdAt)} />\n {isPortalAdmin ? (\n <>\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.roles', 'Role')} value={<Badge className=\"text-overline\">{t('portal.dashboard.portalAdmin', 'Portal Admin')}</Badge>} />\n </>\n ) : null}\n </div>\n </PortalCard>\n\n <PortalCard>\n <PortalCardHeader label={t('portal.dashboard.roles', 'Roles')} title={`${roles.length} assigned`} />\n {roles.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {roles.map((role) => (\n <span key={role.id} className=\"inline-flex items-center rounded-lg border px-3 py-1.5 text-xs font-medium\">{role.name}</span>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('portal.dashboard.noRoles', 'No roles assigned')}</p>\n )}\n </PortalCard>\n\n <PortalCard className=\"md:col-span-2\">\n <PortalCardHeader label={t('portal.dashboard.permissions', 'Permissions')} title={`${resolvedFeatures.length} features`} />\n {resolvedFeatures.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {resolvedFeatures.map((feature) => (\n <span key={feature} className=\"inline-flex items-center rounded-md border bg-muted/50 px-2 py-1 font-mono text-overline text-muted-foreground\">{feature}</span>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('portal.dashboard.noPermissions', 'No permissions')}</p>\n )}\n </PortalCard>\n </div>\n\n <InjectionSpot spotId={extensionPoints.hosts.profileAfter.spotId} context={injectionContext} />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAiCmE,SAyCrD,UAzCqD,KA4BnD,YA5BmD;AAhCnE,SAAS,WAAW,eAAe;AACnC,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AACjC,SAAS,YAAY,kBAAkB,eAAe,yBAAyB;AAC/E,SAAS,qBAAqB;AAIf,SAAR,kBAAmC,EAAE,OAAO,GAAU;AAC3D,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAClC,QAAM,EAAE,MAAM,OAAO,kBAAkB,eAAe,QAAQ,IAAI;AAIlE,YAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,MAAM;AACrB,4BAAsB,IAAI,OAAO,OAAO,eAAe;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,SAAS,MAAM,OAAO,OAAO,CAAC;AAElC,QAAM,mBAAmB;AAAA,IACvB,OAAO,EAAE,SAAS,OAAO,SAAS,MAAM,OAAO,kBAAkB,cAAc;AAAA,IAC/E,CAAC,OAAO,SAAS,MAAM,OAAO,kBAAkB,aAAa;AAAA,EAC/D;AAEA,MAAI,SAAS;AACX,WAAO,oBAAC,SAAI,WAAU,0CAAyC,8BAAC,WAAQ,GAAE;AAAA,EAC5E;AAEA,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,aAAa,CAAC,eAA8B;AAChD,QAAI,CAAC,WAAY,QAAO,EAAE,0BAA0B,OAAO;AAC3D,WAAO,IAAI,KAAK,UAAU,EAAE,mBAAmB,QAAW;AAAA,MACxD,MAAM;AAAA,MAAW,OAAO;AAAA,MAAS,KAAK;AAAA,MAAW,MAAM;AAAA,MAAW,QAAQ;AAAA,IAC5E,CAAC;AAAA,EACH;AAEA,SACE,qBAAC,SAAI,WAAU,uBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,wBAAwB,SAAS;AAAA,QAC1C,OAAO,EAAE,wBAAwB,SAAS;AAAA;AAAA,IAC5C;AAAA,IAEA,oBAAC,iBAAc,QAAQ,gBAAgB,MAAM,cAAc,QAAQ,SAAS,kBAAkB;AAAA,IAE9F,qBAAC,SAAI,WAAU,6BACb;AAAA,2BAAC,cACC;AAAA,4BAAC,oBAAiB,OAAO,EAAE,4BAA4B,SAAS,GAAG,OAAO,KAAK,aAAa;AAAA,QAC5F,qBAAC,SAAI,WAAU,iBACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,0BAA0B,OAAO;AAAA,cAC1C,OACE,qBAAC,UAAK,WAAU,2BACd;AAAA,oCAAC,UAAK,WAAU,YAAY,eAAK,OAAM;AAAA,gBACvC,oBAAC,SAAM,SAAS,KAAK,gBAAgB,YAAY,WAAW,WAAU,0BACnE,eAAK,gBAAgB,EAAE,kCAAkC,UAAU,IAAI,EAAE,qCAAqC,YAAY,GAC7H;AAAA,iBACF;AAAA;AAAA,UAEJ;AAAA,UACA,oBAAC,qBAAkB;AAAA,UACnB,oBAAC,iBAAc,OAAO,EAAE,8BAA8B,YAAY,GAAG,OAAO,WAAW,KAAK,WAAW,GAAG;AAAA,UAC1G,oBAAC,qBAAkB;AAAA,UACnB,oBAAC,iBAAc,OAAO,EAAE,gCAAgC,cAAc,GAAG,OAAO,WAAW,KAAK,SAAS,GAAG;AAAA,UAC3G,gBACC,iCACE;AAAA,gCAAC,qBAAkB;AAAA,YACnB,oBAAC,iBAAc,OAAO,EAAE,0BAA0B,MAAM,GAAG,OAAO,oBAAC,SAAM,WAAU,iBAAiB,YAAE,gCAAgC,cAAc,GAAE,GAAU;AAAA,aAClK,IACE;AAAA,WACN;AAAA,SACF;AAAA,MAEA,qBAAC,cACC;AAAA,4BAAC,oBAAiB,OAAO,EAAE,0BAA0B,OAAO,GAAG,OAAO,GAAG,MAAM,MAAM,aAAa;AAAA,QACjG,MAAM,SAAS,IACd,oBAAC,SAAI,WAAU,wBACZ,gBAAM,IAAI,CAAC,SACV,oBAAC,UAAmB,WAAU,8EAA8E,eAAK,QAAtG,KAAK,EAAsG,CACvH,GACH,IAEA,oBAAC,OAAE,WAAU,iCAAiC,YAAE,4BAA4B,mBAAmB,GAAE;AAAA,SAErG;AAAA,MAEA,qBAAC,cAAW,WAAU,iBACpB;AAAA,4BAAC,oBAAiB,OAAO,EAAE,gCAAgC,aAAa,GAAG,OAAO,GAAG,iBAAiB,MAAM,aAAa;AAAA,QACxH,iBAAiB,SAAS,IACzB,oBAAC,SAAI,WAAU,wBACZ,2BAAiB,IAAI,CAAC,YACrB,oBAAC,UAAmB,WAAU,kHAAkH,qBAArI,OAA6I,CACzJ,GACH,IAEA,oBAAC,OAAE,WAAU,iCAAiC,YAAE,kCAAkC,gBAAgB,GAAE;AAAA,SAExG;AAAA,OACF;AAAA,IAEA,oBAAC,iBAAc,QAAQ,gBAAgB,MAAM,aAAa,QAAQ,SAAS,kBAAkB;AAAA,KAC/F;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
window.location.assign(path);
|
|
3
|
-
}
|
|
1
|
+
import { navigateWithPageReload, replaceWithPageReload } from "@open-mercato/shared/lib/navigation/pageReload";
|
|
4
2
|
export {
|
|
5
|
-
navigateWithPageReload
|
|
3
|
+
navigateWithPageReload,
|
|
4
|
+
replaceWithPageReload
|
|
6
5
|
};
|
|
7
6
|
//# sourceMappingURL=navigation.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/portal/lib/navigation.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * Portal-local import path for the boundary-crossing navigation helpers.\n *\n * The implementation lives in `@open-mercato/shared/lib/navigation/pageReload` so `@open-mercato/ui`\n * \u2014 which does not depend on `@open-mercato/core` \u2014 can reach it from the portal shell and hooks.\n * This re-export keeps the published import path stable for existing callers.\n */\nexport { navigateWithPageReload, replaceWithPageReload } from '@open-mercato/shared/lib/navigation/pageReload'\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,wBAAwB,6BAA6B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,12 @@ import { sql } from "kysely";
|
|
|
5
5
|
import { readCoverageSnapshot, refreshCoverageSnapshot } from "./coverage.js";
|
|
6
6
|
import { createProfiler, shouldEnableProfiler } from "@open-mercato/shared/lib/profiler";
|
|
7
7
|
import { decryptIndexDocCustomFields } from "@open-mercato/shared/lib/encryption/indexDoc";
|
|
8
|
+
import {
|
|
9
|
+
buildCustomFieldKindIndex,
|
|
10
|
+
createCustomFieldKindMapResolver,
|
|
11
|
+
emptyCustomFieldKindIndex,
|
|
12
|
+
mergeCustomFieldKindIndexes
|
|
13
|
+
} from "@open-mercato/shared/lib/custom-fields/kinds";
|
|
8
14
|
import { parseBooleanToken, parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
9
15
|
import {
|
|
10
16
|
applyJoinFilters,
|
|
@@ -138,7 +144,7 @@ class HybridQueryEngine {
|
|
|
138
144
|
this.eventBusResolver = eventBusResolver;
|
|
139
145
|
this.vectorServiceResolver = vectorServiceResolver;
|
|
140
146
|
this.encryptionResolver = encryptionResolver;
|
|
141
|
-
this.
|
|
147
|
+
this.customFieldDefsCache = /* @__PURE__ */ new Map();
|
|
142
148
|
this.columnCache = /* @__PURE__ */ new Map();
|
|
143
149
|
this.customEntityCache = /* @__PURE__ */ new Map();
|
|
144
150
|
this.debugVerbosity = null;
|
|
@@ -968,6 +974,16 @@ class HybridQueryEngine {
|
|
|
968
974
|
const total = counted.total;
|
|
969
975
|
const listCountCapWarning = counted.warning;
|
|
970
976
|
const dekKeyCache = /* @__PURE__ */ new Map();
|
|
977
|
+
let customFieldKinds = null;
|
|
978
|
+
const resolveCustomFieldKinds = () => {
|
|
979
|
+
if (!customFieldKinds) {
|
|
980
|
+
customFieldKinds = this.resolveCustomFieldKindIndex(
|
|
981
|
+
Array.from(new Set(indexSources.map((src) => String(src.entityId)))),
|
|
982
|
+
opts.tenantId ?? null
|
|
983
|
+
).then((index) => createCustomFieldKindMapResolver(index));
|
|
984
|
+
}
|
|
985
|
+
return customFieldKinds;
|
|
986
|
+
};
|
|
971
987
|
const decryptRow = async (item) => {
|
|
972
988
|
let next = item;
|
|
973
989
|
if (encSvc?.decryptEntityPayload) {
|
|
@@ -986,14 +1002,17 @@ class HybridQueryEngine {
|
|
|
986
1002
|
}
|
|
987
1003
|
if (encSvc) {
|
|
988
1004
|
try {
|
|
1005
|
+
const cfScope = {
|
|
1006
|
+
tenantId: next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
|
|
1007
|
+
organizationId: next?.organization_id ?? next?.organizationId ?? fallbackOrgId ?? null
|
|
1008
|
+
};
|
|
1009
|
+
const resolveKinds = await resolveCustomFieldKinds();
|
|
989
1010
|
next = await decryptIndexDocCustomFields(
|
|
990
1011
|
next,
|
|
991
|
-
|
|
992
|
-
tenantId: next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
|
|
993
|
-
organizationId: next?.organization_id ?? next?.organizationId ?? null
|
|
994
|
-
},
|
|
1012
|
+
cfScope,
|
|
995
1013
|
encSvc,
|
|
996
|
-
dekKeyCache
|
|
1014
|
+
dekKeyCache,
|
|
1015
|
+
resolveKinds(cfScope.organizationId, cfScope.tenantId)
|
|
997
1016
|
);
|
|
998
1017
|
} catch {
|
|
999
1018
|
}
|
|
@@ -1738,29 +1757,75 @@ class HybridQueryEngine {
|
|
|
1738
1757
|
if (present) baseTableExistsCache.set(table, true);
|
|
1739
1758
|
return present;
|
|
1740
1759
|
}
|
|
1741
|
-
async
|
|
1760
|
+
async resolveCustomFieldDefs(entityIds, tenantId) {
|
|
1742
1761
|
if (!entityIds.length) return [];
|
|
1743
1762
|
const cacheKey = this.customFieldKeysCacheKey(entityIds, tenantId);
|
|
1744
1763
|
const now = Date.now();
|
|
1745
|
-
const cached = this.
|
|
1764
|
+
const cached = this.customFieldDefsCache.get(cacheKey);
|
|
1746
1765
|
if (cached && cached.expiresAt > now) return cached.value.slice();
|
|
1747
1766
|
const db = this.getDb();
|
|
1748
|
-
const rows = await db.selectFrom("custom_field_defs").select("key").where("entity_id", "in", entityIds).where("is_active", "=", true).where((eb) => eb.or([
|
|
1767
|
+
const rows = await db.selectFrom("custom_field_defs").select(["entity_id", "key", "kind", "organization_id", "tenant_id", "config_json", "updated_at"]).where("entity_id", "in", entityIds).where("is_active", "=", true).where((eb) => eb.or([
|
|
1749
1768
|
eb("tenant_id", "=", tenantId),
|
|
1750
1769
|
eb("tenant_id", "is", null)
|
|
1751
1770
|
])).execute();
|
|
1752
|
-
const
|
|
1771
|
+
const result = [];
|
|
1753
1772
|
for (const row of rows) {
|
|
1754
1773
|
const key = row.key;
|
|
1755
|
-
|
|
1756
|
-
|
|
1774
|
+
const normalized = typeof key === "string" ? key.trim() : key == null ? "" : String(key);
|
|
1775
|
+
if (!normalized.length) continue;
|
|
1776
|
+
const entityId = typeof row.entity_id === "string" ? row.entity_id : String(row.entity_id ?? "");
|
|
1777
|
+
result.push({
|
|
1778
|
+
entityId,
|
|
1779
|
+
key: normalized,
|
|
1780
|
+
kind: row.kind,
|
|
1781
|
+
organizationId: typeof row.organization_id === "string" ? row.organization_id : null,
|
|
1782
|
+
tenantId: typeof row.tenant_id === "string" ? row.tenant_id : null,
|
|
1783
|
+
configJson: row.config_json ?? null,
|
|
1784
|
+
updatedAt: row.updated_at ?? null
|
|
1785
|
+
});
|
|
1757
1786
|
}
|
|
1758
|
-
const result = Array.from(keys);
|
|
1759
1787
|
if (this.customFieldKeysTtlMs > 0) {
|
|
1760
|
-
this.
|
|
1788
|
+
this.customFieldDefsCache.set(cacheKey, { expiresAt: now + this.customFieldKeysTtlMs, value: result });
|
|
1761
1789
|
}
|
|
1762
1790
|
return result.slice();
|
|
1763
1791
|
}
|
|
1792
|
+
async resolveAvailableCustomFieldKeys(entityIds, tenantId) {
|
|
1793
|
+
const defs = await this.resolveCustomFieldDefs(entityIds, tenantId);
|
|
1794
|
+
return Array.from(new Set(defs.map((def) => def.key)));
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Kind lookup for the `cf:`/`cf_` keys of an index document, so encrypted string-typed
|
|
1798
|
+
* custom fields keep their type on read instead of being `JSON.parse`d back into numbers
|
|
1799
|
+
* or booleans (issue #5968). Backed by the same TTL cache as the key lookup, so this adds
|
|
1800
|
+
* no round trip on a warm cache and fails open to an empty index on a cold-cache error.
|
|
1801
|
+
*
|
|
1802
|
+
* The index keeps every scope's definition per key rather than collapsing to one kind:
|
|
1803
|
+
* the caller resolves the winner against each row's own `organization_id`, the same scope
|
|
1804
|
+
* the row is decrypted with, so an organization's override never types another
|
|
1805
|
+
* organization's value.
|
|
1806
|
+
*
|
|
1807
|
+
* `entityIds` MUST arrive in `indexSources` order: `buildCfJsonExprSql` reads a `cf` value
|
|
1808
|
+
* with `coalesce(source0, source1, …)`, so a key defined by several sources has to be typed
|
|
1809
|
+
* by the same source the value came from — hence the priority-ordered merge rather than
|
|
1810
|
+
* whatever order the rows happen to come back in.
|
|
1811
|
+
*/
|
|
1812
|
+
async resolveCustomFieldKindIndex(entityIds, tenantId) {
|
|
1813
|
+
try {
|
|
1814
|
+
const defs = await this.resolveCustomFieldDefs(entityIds, tenantId);
|
|
1815
|
+
const byEntityId = /* @__PURE__ */ new Map();
|
|
1816
|
+
for (const def of defs) {
|
|
1817
|
+
const group = byEntityId.get(def.entityId) ?? [];
|
|
1818
|
+
group.push(def);
|
|
1819
|
+
byEntityId.set(def.entityId, group);
|
|
1820
|
+
}
|
|
1821
|
+
return mergeCustomFieldKindIndexes(
|
|
1822
|
+
entityIds.map((entityId) => buildCustomFieldKindIndex(byEntityId.get(entityId) ?? []))
|
|
1823
|
+
);
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
logger.warn("Failed to resolve custom field kinds", { entityIds, err });
|
|
1826
|
+
return emptyCustomFieldKindIndex();
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1764
1829
|
async entityHasActiveCustomFields(entityId, tenantId) {
|
|
1765
1830
|
try {
|
|
1766
1831
|
const keys = await this.resolveAvailableCustomFieldKeys([entityId], tenantId);
|