@open-mercato/core 0.6.2-develop.3461.1.605f31c2c9 → 0.6.3-canary.3483.1.98d4b4ecc7

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.
Files changed (53) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/frontend/login.js +35 -0
  3. package/dist/modules/auth/frontend/login.js.map +2 -2
  4. package/dist/modules/business_rules/lib/rule-evaluator.js +3 -3
  5. package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
  6. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +13 -5
  7. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  8. package/dist/modules/integrations/data/validators.js +9 -6
  9. package/dist/modules/integrations/data/validators.js.map +2 -2
  10. package/dist/modules/messages/commands/messages.js +42 -2
  11. package/dist/modules/messages/commands/messages.js.map +2 -2
  12. package/dist/modules/messages/components/MessagesInboxPageClient.js +46 -88
  13. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  14. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +8 -6
  15. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +2 -2
  16. package/dist/modules/messages/components/inboxFilters.js +133 -0
  17. package/dist/modules/messages/components/inboxFilters.js.map +7 -0
  18. package/dist/modules/messages/components/messageListLabels.js +14 -0
  19. package/dist/modules/messages/components/messageListLabels.js.map +7 -0
  20. package/dist/modules/messages/components/useMessagesInboxBulkActions.js +47 -23
  21. package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +2 -2
  22. package/dist/modules/messages/data/validators.js +2 -1
  23. package/dist/modules/messages/data/validators.js.map +2 -2
  24. package/dist/modules/sales/api/order-adjustments/route.js.map +2 -2
  25. package/dist/modules/sales/api/quote-adjustments/route.js.map +2 -2
  26. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
  27. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  28. package/jest.config.cjs +3 -1
  29. package/package.json +15 -15
  30. package/src/modules/auth/frontend/login.tsx +40 -0
  31. package/src/modules/business_rules/lib/rule-evaluator.ts +3 -3
  32. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +18 -7
  33. package/src/modules/integrations/data/validators.ts +5 -4
  34. package/src/modules/messages/commands/messages.ts +54 -2
  35. package/src/modules/messages/components/MessagesInboxPageClient.tsx +51 -91
  36. package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +9 -5
  37. package/src/modules/messages/components/inboxFilters.ts +162 -0
  38. package/src/modules/messages/components/messageListLabels.ts +29 -0
  39. package/src/modules/messages/components/useMessagesInboxBulkActions.ts +58 -26
  40. package/src/modules/messages/data/validators.ts +1 -0
  41. package/src/modules/messages/i18n/de.json +7 -4
  42. package/src/modules/messages/i18n/en.json +7 -4
  43. package/src/modules/messages/i18n/es.json +7 -4
  44. package/src/modules/messages/i18n/pl.json +7 -4
  45. package/src/modules/sales/api/order-adjustments/route.ts +2 -1
  46. package/src/modules/sales/api/quote-adjustments/route.ts +2 -1
  47. package/src/modules/sales/lib/makeSalesLineRoute.ts +2 -1
  48. package/src/modules/staff/i18n/de.json +1 -0
  49. package/src/modules/staff/i18n/en.json +1 -0
  50. package/src/modules/staff/i18n/es.json +1 -0
  51. package/src/modules/staff/i18n/pl.json +1 -0
  52. package/src/modules/workflows/lib/activity-executor.ts +4 -3
  53. package/tsconfig.json +1 -0
@@ -1,4 +1,4 @@
1
- [build:core] found 2549 entry points
1
+ [build:core] found 2551 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 172 entry points
4
4
  [build:core:generated] built successfully
@@ -12,6 +12,7 @@ import { Button } from "@open-mercato/ui/primitives/button";
12
12
  import { useT } from "@open-mercato/shared/lib/i18n/context";
13
13
  import { translateWithFallback } from "@open-mercato/shared/lib/i18n/translate";
14
14
  import { clearAllOperations } from "@open-mercato/ui/backend/operations/store";
15
+ import { notifyAuthIdentityChange } from "@open-mercato/ui/backend/AuthSessionGuard";
15
16
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
16
17
  import { X } from "lucide-react";
17
18
  import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
@@ -105,6 +106,38 @@ function LoginPage() {
105
106
  useEffect(() => {
106
107
  setClientReady(true);
107
108
  }, []);
109
+ useEffect(() => {
110
+ let cancelled = false;
111
+ void (async () => {
112
+ try {
113
+ const res = await apiCall("/api/auth/feature-check", {
114
+ method: "POST",
115
+ headers: { "content-type": "application/json" },
116
+ body: JSON.stringify({ features: [] }),
117
+ cache: "no-store"
118
+ });
119
+ if (cancelled) return;
120
+ const activeUserId = typeof res.result?.userId === "string" ? res.result.userId : "";
121
+ if (!activeUserId) return;
122
+ const rawRedirect = searchParams.get("redirect") || "";
123
+ let destination = "/backend";
124
+ if (rawRedirect) {
125
+ try {
126
+ const resolved = new URL(rawRedirect, window.location.origin);
127
+ if (resolved.origin === window.location.origin && resolved.pathname.startsWith("/") && !resolved.pathname.includes("//")) {
128
+ destination = resolved.pathname + resolved.search + resolved.hash;
129
+ }
130
+ } catch {
131
+ }
132
+ }
133
+ router.replace(destination);
134
+ } catch {
135
+ }
136
+ })();
137
+ return () => {
138
+ cancelled = true;
139
+ };
140
+ }, [router, searchParams]);
108
141
  useEffect(() => {
109
142
  const tenantParam = (searchParams.get("tenant") || "").trim();
110
143
  if (tenantParam) {
@@ -186,6 +219,7 @@ function LoginPage() {
186
219
  const res = await fetch("/api/auth/login", { method: "POST", body: form });
187
220
  if (res.redirected) {
188
221
  clearAllOperations();
222
+ notifyAuthIdentityChange();
189
223
  router.replace(res.url);
190
224
  return;
191
225
  }
@@ -237,6 +271,7 @@ function LoginPage() {
237
271
  const data = await res.json().catch(() => null);
238
272
  emitLoginResponseEvent(data);
239
273
  clearAllOperations();
274
+ notifyAuthIdentityChange();
240
275
  if (data && typeof data.redirect === "string" && data.redirect.length > 0) {
241
276
  router.replace(data.redirect);
242
277
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/frontend/login.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\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 { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { X } from 'lucide-react'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport type { AuthOverride, LoginFormWidgetContext } from './login-injection'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\ntype LoginResponseEventDetail = Record<string, unknown> | null\n\ntype LoginFormSectionProps = {\n children: ReactNode\n}\n\nfunction LoginFormSectionDefault({ children }: LoginFormSectionProps) {\n return <>{children}</>\n}\n\nfunction emitLoginResponseEvent(detail: LoginResponseEventDetail) {\n if (typeof window === 'undefined') return\n window.dispatchEvent(new CustomEvent('om:auth:login-response', { detail }))\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [authOverride, setAuthOverride] = useState<AuthOverride | null>(null)\n const [authOverridePending, setAuthOverridePending] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const [email, setEmail] = useState('')\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)\n const showTenantInvalid = tenantId != null && tenantInvalid === tenantId\n const LoginFormSection = useRegisteredComponent<LoginFormSectionProps>(\n 'section:auth.login.form',\n LoginFormSectionDefault,\n )\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n setTenantInvalid(null)\n return\n }\n if (tenantInvalid === tenantId) {\n setTenantName(null)\n setTenantLoading(false)\n return\n }\n let active = true\n setTenantLoading(true)\n setTenantInvalid(null)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n setTenantInvalid(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n if (!clientReady || authOverridePending) {\n return\n }\n setError(null)\n if (authOverride) {\n authOverride.onSubmit()\n return\n }\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const redirectParam = searchParams.get('redirect')\n if (redirectParam) form.set('redirect', redirectParam)\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null) as LoginResponseEventDetail\n emitLoginResponseEvent(data)\n clearAllOperations()\n if (data && typeof data.redirect === 'string' && data.redirect.length > 0) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n const loginFormContext = useMemo<LoginFormWidgetContext>(() => ({\n email,\n tenantId,\n searchParams,\n setAuthOverride,\n setAuthOverridePending,\n setError,\n }), [email, tenantId, searchParams])\n\n const formReady = clientReady && !authOverridePending\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <LoginFormSection>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate data-auth-ready={formReady ? '1' : '0'}>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </AlertDescription>\n </Alert>\n )}\n {!!translatedFeatures.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </AlertDescription>\n </Alert>\n )}\n {showTenantInvalid ? (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700\">\n <div className=\"font-medium\">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-red-300 text-red-700\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : tenantId ? (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-emerald-300 text-emerald-900\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : null}\n {error && !showTenantInvalid && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <EmailInput\n id=\"email\"\n name=\"email\"\n required\n aria-invalid={!!error}\n onChange={(e) => setEmail(e.target.value)}\n onBlur={(e) => setEmail(e.target.value)}\n />\n </div>\n <InjectionSpot<LoginFormWidgetContext>\n spotId=\"auth.login:form\"\n context={loginFormContext}\n />\n {authOverride?.hidePassword ? null : (\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required={!authOverride} aria-invalid={!!error} autoComplete=\"current-password\" />\n </div>\n )}\n {!authOverride?.hideRememberMe && !authOverride?.hidePassword && (\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n )}\n <Button type=\"submit\" disabled={submitting || !formReady} className=\"h-10 mt-2\">\n {submitting\n ? translate('auth.login.loading', 'Loading...')\n : authOverride\n ? authOverride.providerLabel\n : translate('auth.signIn', 'Sign in')}\n </Button>\n {!authOverride?.hideForgotPassword && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n )}\n </form>\n </LoginFormSection>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\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 { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { notifyAuthIdentityChange } from '@open-mercato/ui/backend/AuthSessionGuard'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { X } from 'lucide-react'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport type { AuthOverride, LoginFormWidgetContext } from './login-injection'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\ntype LoginResponseEventDetail = Record<string, unknown> | null\n\ntype LoginFormSectionProps = {\n children: ReactNode\n}\n\nfunction LoginFormSectionDefault({ children }: LoginFormSectionProps) {\n return <>{children}</>\n}\n\nfunction emitLoginResponseEvent(detail: LoginResponseEventDetail) {\n if (typeof window === 'undefined') return\n window.dispatchEvent(new CustomEvent('om:auth:login-response', { detail }))\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [authOverride, setAuthOverride] = useState<AuthOverride | null>(null)\n const [authOverridePending, setAuthOverridePending] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const [email, setEmail] = useState('')\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)\n const showTenantInvalid = tenantId != null && tenantInvalid === tenantId\n const LoginFormSection = useRegisteredComponent<LoginFormSectionProps>(\n 'section:auth.login.form',\n LoginFormSectionDefault,\n )\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const res = await apiCall<{ userId?: string }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: [] }),\n cache: 'no-store',\n })\n if (cancelled) return\n const activeUserId = typeof res.result?.userId === 'string' ? res.result.userId : ''\n if (!activeUserId) return\n const rawRedirect = searchParams.get('redirect') || ''\n let destination = '/backend'\n if (rawRedirect) {\n try {\n const resolved = new URL(rawRedirect, window.location.origin)\n if (\n resolved.origin === window.location.origin &&\n resolved.pathname.startsWith('/') &&\n !resolved.pathname.includes('//')\n ) {\n destination = resolved.pathname + resolved.search + resolved.hash\n }\n } catch {\n // fall back to /backend\n }\n }\n router.replace(destination)\n } catch {\n // ignore \u2014 leave login form usable on network failure\n }\n })()\n return () => { cancelled = true }\n }, [router, searchParams])\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n setTenantInvalid(null)\n return\n }\n if (tenantInvalid === tenantId) {\n setTenantName(null)\n setTenantLoading(false)\n return\n }\n let active = true\n setTenantLoading(true)\n setTenantInvalid(null)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n setTenantInvalid(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n if (!clientReady || authOverridePending) {\n return\n }\n setError(null)\n if (authOverride) {\n authOverride.onSubmit()\n return\n }\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const redirectParam = searchParams.get('redirect')\n if (redirectParam) form.set('redirect', redirectParam)\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n notifyAuthIdentityChange()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null) as LoginResponseEventDetail\n emitLoginResponseEvent(data)\n clearAllOperations()\n notifyAuthIdentityChange()\n if (data && typeof data.redirect === 'string' && data.redirect.length > 0) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n const loginFormContext = useMemo<LoginFormWidgetContext>(() => ({\n email,\n tenantId,\n searchParams,\n setAuthOverride,\n setAuthOverridePending,\n setError,\n }), [email, tenantId, searchParams])\n\n const formReady = clientReady && !authOverridePending\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <LoginFormSection>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate data-auth-ready={formReady ? '1' : '0'}>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </AlertDescription>\n </Alert>\n )}\n {!!translatedFeatures.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </AlertDescription>\n </Alert>\n )}\n {showTenantInvalid ? (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700\">\n <div className=\"font-medium\">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-red-300 text-red-700\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : tenantId ? (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-emerald-300 text-emerald-900\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : null}\n {error && !showTenantInvalid && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <EmailInput\n id=\"email\"\n name=\"email\"\n required\n aria-invalid={!!error}\n onChange={(e) => setEmail(e.target.value)}\n onBlur={(e) => setEmail(e.target.value)}\n />\n </div>\n <InjectionSpot<LoginFormWidgetContext>\n spotId=\"auth.login:form\"\n context={loginFormContext}\n />\n {authOverride?.hidePassword ? null : (\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required={!authOverride} aria-invalid={!!error} autoComplete=\"current-password\" />\n </div>\n )}\n {!authOverride?.hideRememberMe && !authOverride?.hidePassword && (\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n )}\n <Button type=\"submit\" disabled={submitting || !formReady} className=\"h-10 mt-2\">\n {submitting\n ? translate('auth.login.loading', 'Loading...')\n : authOverride\n ? authOverride.providerLabel\n : translate('auth.signIn', 'Sign in')}\n </Button>\n {!authOverride?.hideForgotPassword && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n )}\n </form>\n </LoginFormSection>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["data"]
7
7
  }
@@ -26,7 +26,7 @@ async function evaluate(rules, data, context) {
26
26
  failedRules.push(rule);
27
27
  }
28
28
  }
29
- const evaluationTime = Date.now() - startTime;
29
+ const evaluationTime = Math.max(0, Date.now() - startTime);
30
30
  const conditionsPassed = matchedRules.length > 0;
31
31
  return {
32
32
  conditionsPassed,
@@ -70,7 +70,7 @@ async function evaluateSingleRule(rule, data, context) {
70
70
  };
71
71
  }
72
72
  const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context);
73
- const evaluationTime = Date.now() - startTime;
73
+ const evaluationTime = Math.max(0, Date.now() - startTime);
74
74
  console.log("[RULE EVAL] Final result:", {
75
75
  ruleId: rule.ruleId,
76
76
  conditionsPassed: conditionsPassed ? "\u2713 PASS" : "\u2717 FAIL",
@@ -84,7 +84,7 @@ async function evaluateSingleRule(rule, data, context) {
84
84
  evaluationTime
85
85
  };
86
86
  } catch (error) {
87
- const evaluationTime = Date.now() - startTime;
87
+ const evaluationTime = Math.max(0, Date.now() - startTime);
88
88
  const errorMessage = error instanceof Error ? error.message : String(error);
89
89
  console.log("[RULE EVAL] \u2717 ERROR:", errorMessage);
90
90
  console.log("[RULE EVAL] ========================================");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/business_rules/lib/rule-evaluator.ts"],
4
- "sourcesContent": ["import type { BusinessRule } from '../data/entities'\nimport { evaluateExpression, type EvaluationContext, type ConditionExpression } from './expression-evaluator'\n\n/**\n * Rule evaluation context\n */\nexport interface RuleEvaluationContext extends EvaluationContext {\n entityType?: string\n entityId?: string\n eventType?: string\n [key: string]: any\n}\n\n/**\n * Rule evaluation result (for evaluating multiple rules)\n */\nexport interface RuleEvaluationResult {\n conditionsPassed: boolean // At least one rule matched\n evaluationCompleted: boolean // All rules evaluated without critical errors\n matchedRules: BusinessRule[]\n failedRules: BusinessRule[]\n evaluationTime: number\n errors?: string[]\n}\n\n/**\n * Single rule evaluation result\n */\nexport interface SingleRuleResult {\n rule: BusinessRule\n conditionsPassed: boolean // Logical result: did conditions evaluate to true?\n evaluationCompleted: boolean // Technical success: did evaluation finish without errors?\n evaluationTime: number\n error?: string\n}\n\n/**\n * Evaluate multiple rules and return aggregated results\n */\nexport async function evaluate(\n rules: BusinessRule[],\n data: any,\n context: RuleEvaluationContext\n): Promise<RuleEvaluationResult> {\n const startTime = Date.now()\n const matchedRules: BusinessRule[] = []\n const failedRules: BusinessRule[] = []\n const errors: string[] = []\n let anyEvaluationCompleted = false\n\n // Sort rules by priority (higher priority first)\n const sortedRules = sortRulesByPriority(rules)\n\n // Evaluate each rule\n for (const rule of sortedRules) {\n try {\n const result = await evaluateSingleRule(rule, data, context)\n\n // Track if any rule completed evaluation\n if (result.evaluationCompleted) {\n anyEvaluationCompleted = true\n }\n\n if (result.evaluationCompleted && result.conditionsPassed) {\n matchedRules.push(rule)\n } else {\n failedRules.push(rule)\n }\n\n if (result.error) {\n errors.push(`Rule ${rule.ruleId}: ${result.error}`)\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(`Rule ${rule.ruleId}: ${errorMessage}`)\n failedRules.push(rule)\n }\n }\n\n const evaluationTime = Date.now() - startTime\n\n // Determine if conditions were met (at least one rule matched)\n const conditionsPassed = matchedRules.length > 0\n\n return {\n conditionsPassed,\n evaluationCompleted: anyEvaluationCompleted,\n matchedRules,\n failedRules,\n evaluationTime,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Evaluate a single business rule\n */\nexport async function evaluateSingleRule(\n rule: BusinessRule,\n data: any,\n context: RuleEvaluationContext\n): Promise<SingleRuleResult> {\n const startTime = Date.now()\n\n console.log('[RULE EVAL] ========================================')\n console.log('[RULE EVAL] Evaluating rule:', {\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n ruleType: rule.ruleType,\n entityType: rule.entityType,\n eventType: rule.eventType,\n })\n console.log('[RULE EVAL] Conditions:', JSON.stringify(rule.conditionExpression, null, 2))\n\n try {\n // Check if rule is enabled\n if (!rule.enabled) {\n console.log('[RULE EVAL] \u2717 Rule is disabled')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is disabled',\n }\n }\n\n // Check effective date range\n if (!isRuleEffective(rule)) {\n console.log('[RULE EVAL] \u2717 Rule is not effective (outside date range)')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is not effective (outside date range)',\n }\n }\n\n // Evaluate conditions\n const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context)\n\n const evaluationTime = Date.now() - startTime\n\n console.log('[RULE EVAL] Final result:', {\n ruleId: rule.ruleId,\n conditionsPassed: conditionsPassed ? '\u2713 PASS' : '\u2717 FAIL',\n evaluationTime: `${evaluationTime}ms`,\n })\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed,\n evaluationCompleted: true,\n evaluationTime,\n }\n } catch (error) {\n const evaluationTime = Date.now() - startTime\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n console.log('[RULE EVAL] \u2717 ERROR:', errorMessage)\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime,\n error: errorMessage,\n }\n }\n}\n\n/**\n * Evaluate rule conditions (delegates to expression evaluator)\n */\nexport async function evaluateConditions(\n conditions: any,\n data: any,\n context: RuleEvaluationContext\n): Promise<boolean> {\n if (!conditions) {\n return true\n }\n\n try {\n const expression = conditions as ConditionExpression\n return evaluateExpression(expression, data, context)\n } catch (error) {\n // If evaluation fails, the rule fails\n throw new Error(`Condition evaluation failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\n/**\n * Sort rules by priority (higher priority first, then by ID for stability)\n */\nexport function sortRulesByPriority(rules: BusinessRule[]): BusinessRule[] {\n return [...rules].sort((a, b) => {\n if (b.priority !== a.priority) {\n return b.priority - a.priority\n }\n\n // If priority is the same, sort by ruleId for stability\n return a.ruleId.localeCompare(b.ruleId)\n })\n}\n\n/**\n * Check if rule is within its effective date range\n */\nfunction isRuleEffective(rule: BusinessRule): boolean {\n const now = new Date()\n\n // Check effectiveFrom\n if (rule.effectiveFrom && rule.effectiveFrom > now) {\n return false\n }\n\n // Check effectiveTo\n if (rule.effectiveTo && rule.effectiveTo < now) {\n return false\n }\n\n return true\n}\n\n/**\n * Filter rules by entity type and event type\n */\nexport function filterRulesByContext(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n return rules.filter((rule) => {\n // Filter by entity type\n if (entityType && rule.entityType !== entityType) {\n return false\n }\n\n // Filter by event type (if rule specifies one)\n if (eventType && rule.eventType && rule.eventType !== eventType) {\n return false\n }\n\n return true\n })\n}\n\n/**\n * Get applicable rules for a context (filtered, sorted, and enabled)\n */\nexport function getApplicableRules(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n // Filter by context\n const filtered = filterRulesByContext(rules, entityType, eventType)\n\n // Filter by enabled status\n const enabled = filtered.filter((rule) => rule.enabled)\n\n // Filter by effective date\n const effective = enabled.filter((rule) => isRuleEffective(rule))\n\n // Sort by priority\n return sortRulesByPriority(effective)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { BusinessRule } from '../data/entities'\nimport { evaluateExpression, type EvaluationContext, type ConditionExpression } from './expression-evaluator'\n\n/**\n * Rule evaluation context\n */\nexport interface RuleEvaluationContext extends EvaluationContext {\n entityType?: string\n entityId?: string\n eventType?: string\n [key: string]: any\n}\n\n/**\n * Rule evaluation result (for evaluating multiple rules)\n */\nexport interface RuleEvaluationResult {\n conditionsPassed: boolean // At least one rule matched\n evaluationCompleted: boolean // All rules evaluated without critical errors\n matchedRules: BusinessRule[]\n failedRules: BusinessRule[]\n evaluationTime: number\n errors?: string[]\n}\n\n/**\n * Single rule evaluation result\n */\nexport interface SingleRuleResult {\n rule: BusinessRule\n conditionsPassed: boolean // Logical result: did conditions evaluate to true?\n evaluationCompleted: boolean // Technical success: did evaluation finish without errors?\n evaluationTime: number\n error?: string\n}\n\n/**\n * Evaluate multiple rules and return aggregated results\n */\nexport async function evaluate(\n rules: BusinessRule[],\n data: any,\n context: RuleEvaluationContext\n): Promise<RuleEvaluationResult> {\n const startTime = Date.now()\n const matchedRules: BusinessRule[] = []\n const failedRules: BusinessRule[] = []\n const errors: string[] = []\n let anyEvaluationCompleted = false\n\n // Sort rules by priority (higher priority first)\n const sortedRules = sortRulesByPriority(rules)\n\n // Evaluate each rule\n for (const rule of sortedRules) {\n try {\n const result = await evaluateSingleRule(rule, data, context)\n\n // Track if any rule completed evaluation\n if (result.evaluationCompleted) {\n anyEvaluationCompleted = true\n }\n\n if (result.evaluationCompleted && result.conditionsPassed) {\n matchedRules.push(rule)\n } else {\n failedRules.push(rule)\n }\n\n if (result.error) {\n errors.push(`Rule ${rule.ruleId}: ${result.error}`)\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(`Rule ${rule.ruleId}: ${errorMessage}`)\n failedRules.push(rule)\n }\n }\n\n const evaluationTime = Math.max(0, Date.now() - startTime)\n\n // Determine if conditions were met (at least one rule matched)\n const conditionsPassed = matchedRules.length > 0\n\n return {\n conditionsPassed,\n evaluationCompleted: anyEvaluationCompleted,\n matchedRules,\n failedRules,\n evaluationTime,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Evaluate a single business rule\n */\nexport async function evaluateSingleRule(\n rule: BusinessRule,\n data: any,\n context: RuleEvaluationContext\n): Promise<SingleRuleResult> {\n const startTime = Date.now()\n\n console.log('[RULE EVAL] ========================================')\n console.log('[RULE EVAL] Evaluating rule:', {\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n ruleType: rule.ruleType,\n entityType: rule.entityType,\n eventType: rule.eventType,\n })\n console.log('[RULE EVAL] Conditions:', JSON.stringify(rule.conditionExpression, null, 2))\n\n try {\n // Check if rule is enabled\n if (!rule.enabled) {\n console.log('[RULE EVAL] \u2717 Rule is disabled')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is disabled',\n }\n }\n\n // Check effective date range\n if (!isRuleEffective(rule)) {\n console.log('[RULE EVAL] \u2717 Rule is not effective (outside date range)')\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime: 0,\n error: 'Rule is not effective (outside date range)',\n }\n }\n\n // Evaluate conditions\n const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context)\n\n const evaluationTime = Math.max(0, Date.now() - startTime)\n\n console.log('[RULE EVAL] Final result:', {\n ruleId: rule.ruleId,\n conditionsPassed: conditionsPassed ? '\u2713 PASS' : '\u2717 FAIL',\n evaluationTime: `${evaluationTime}ms`,\n })\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed,\n evaluationCompleted: true,\n evaluationTime,\n }\n } catch (error) {\n const evaluationTime = Math.max(0, Date.now() - startTime)\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n console.log('[RULE EVAL] \u2717 ERROR:', errorMessage)\n console.log('[RULE EVAL] ========================================')\n\n return {\n rule,\n conditionsPassed: false,\n evaluationCompleted: false,\n evaluationTime,\n error: errorMessage,\n }\n }\n}\n\n/**\n * Evaluate rule conditions (delegates to expression evaluator)\n */\nexport async function evaluateConditions(\n conditions: any,\n data: any,\n context: RuleEvaluationContext\n): Promise<boolean> {\n if (!conditions) {\n return true\n }\n\n try {\n const expression = conditions as ConditionExpression\n return evaluateExpression(expression, data, context)\n } catch (error) {\n // If evaluation fails, the rule fails\n throw new Error(`Condition evaluation failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\n/**\n * Sort rules by priority (higher priority first, then by ID for stability)\n */\nexport function sortRulesByPriority(rules: BusinessRule[]): BusinessRule[] {\n return [...rules].sort((a, b) => {\n if (b.priority !== a.priority) {\n return b.priority - a.priority\n }\n\n // If priority is the same, sort by ruleId for stability\n return a.ruleId.localeCompare(b.ruleId)\n })\n}\n\n/**\n * Check if rule is within its effective date range\n */\nfunction isRuleEffective(rule: BusinessRule): boolean {\n const now = new Date()\n\n // Check effectiveFrom\n if (rule.effectiveFrom && rule.effectiveFrom > now) {\n return false\n }\n\n // Check effectiveTo\n if (rule.effectiveTo && rule.effectiveTo < now) {\n return false\n }\n\n return true\n}\n\n/**\n * Filter rules by entity type and event type\n */\nexport function filterRulesByContext(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n return rules.filter((rule) => {\n // Filter by entity type\n if (entityType && rule.entityType !== entityType) {\n return false\n }\n\n // Filter by event type (if rule specifies one)\n if (eventType && rule.eventType && rule.eventType !== eventType) {\n return false\n }\n\n return true\n })\n}\n\n/**\n * Get applicable rules for a context (filtered, sorted, and enabled)\n */\nexport function getApplicableRules(\n rules: BusinessRule[],\n entityType?: string,\n eventType?: string\n): BusinessRule[] {\n // Filter by context\n const filtered = filterRulesByContext(rules, entityType, eventType)\n\n // Filter by enabled status\n const enabled = filtered.filter((rule) => rule.enabled)\n\n // Filter by effective date\n const effective = enabled.filter((rule) => isRuleEffective(rule))\n\n // Sort by priority\n return sortRulesByPriority(effective)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,4 @@
1
1
  import * as React from "react";
2
- import { format } from "date-fns";
3
2
  const PARTICIPANT_COLORS = [
4
3
  "bg-chart-emerald",
5
4
  "bg-chart-blue",
@@ -14,10 +13,19 @@ const DEFAULT_REMINDER_MINUTES = {
14
13
  task: 1440,
15
14
  email: 15
16
15
  };
16
+ function padDatePart(value) {
17
+ return String(value).padStart(2, "0");
18
+ }
19
+ function formatLocalDateInput(date) {
20
+ return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`;
21
+ }
22
+ function formatLocalTimeInput(date) {
23
+ return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`;
24
+ }
17
25
  function useScheduleFormState({ open, editData }) {
18
26
  const [activityType, setActivityType] = React.useState("meeting");
19
27
  const [title, setTitle] = React.useState("");
20
- const [date, setDate] = React.useState(() => format(/* @__PURE__ */ new Date(), "yyyy-MM-dd"));
28
+ const [date, setDate] = React.useState(() => formatLocalDateInput(/* @__PURE__ */ new Date()));
21
29
  const [startTime, setStartTime] = React.useState("10:00");
22
30
  const [duration, setDuration] = React.useState(30);
23
31
  const [allDay, setAllDay] = React.useState(false);
@@ -47,8 +55,8 @@ function useScheduleFormState({ open, editData }) {
47
55
  const seedDateValid = !Number.isNaN(seedDate.getTime());
48
56
  const fallbackNow = /* @__PURE__ */ new Date();
49
57
  const dateForForm = seedDateValid ? seedDate : fallbackNow;
50
- setDate(format(dateForForm, "yyyy-MM-dd"));
51
- setStartTime(format(dateForForm, "HH:mm"));
58
+ setDate(formatLocalDateInput(dateForForm));
59
+ setStartTime(formatLocalTimeInput(dateForForm));
52
60
  setDuration(editData.durationMinutes ?? 30);
53
61
  setAllDay(editData.allDay ?? false);
54
62
  setDescription(editData.body ?? "");
@@ -101,7 +109,7 @@ function useScheduleFormState({ open, editData }) {
101
109
  } else {
102
110
  setActivityType("meeting");
103
111
  setTitle("");
104
- setDate(format(/* @__PURE__ */ new Date(), "yyyy-MM-dd"));
112
+ setDate(formatLocalDateInput(/* @__PURE__ */ new Date()));
105
113
  setStartTime("10:00");
106
114
  setDuration(30);
107
115
  setAllDay(false);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/components/detail/schedule/useScheduleFormState.ts"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { format } from 'date-fns'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n /**\n * Historical timestamp for completed activities (status `done`). Required for\n * the edit prefill to restore the original date/time instead of falling back\n * to \"today\" (#1807).\n */\n occurredAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\n// Per-Figma defaults for the Reminder dropdown when the user picks an activity\n// type. Meeting/email keep the standard 15 min; tasks default to 1 day (1440 min)\n// because they're plan-ahead artefacts; calls default to 5 min as a stand-in for\n// the Figma \"After call ends\" treatment (which would need a non-numeric sentinel\n// in the API contract \u2014 tracked as a follow-up).\nconst DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {\n meeting: 15,\n call: 5,\n task: 1440,\n email: 15,\n}\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => format(new Date(), 'yyyy-MM-dd'))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n const resolvedType = (editData.interactionType as ActivityType) ?? 'meeting'\n setActivityType(resolvedType)\n setTitle(editData.title ?? '')\n // For historical activities the canonical timestamp is `occurredAt`; for\n // planned/future ones it's `scheduledAt`. Without this fallback editing a\n // past activity prefilled to \"today\" instead of its actual moment (#1807).\n // Use `date-fns` `format(...)` so the seed values are in the user's local\n // timezone (matches the cluster-E local-day convention).\n const sourceTimestamp = editData.occurredAt ?? editData.scheduledAt ?? null\n const seedDate = sourceTimestamp ? new Date(sourceTimestamp) : new Date()\n const seedDateValid = !Number.isNaN(seedDate.getTime())\n const fallbackNow = new Date()\n const dateForForm = seedDateValid ? seedDate : fallbackNow\n setDate(format(dateForForm, 'yyyy-MM-dd'))\n setStartTime(format(dateForForm, 'HH:mm'))\n setDuration(editData.durationMinutes ?? 30)\n setAllDay(editData.allDay ?? false)\n setDescription(editData.body ?? '')\n setLocation(editData.location ?? '')\n // Use per-type default when the editData omits an explicit reminder\n // (the menu-driven \"New X\" flow opens the dialog with `reminderMinutes: null`).\n setReminderMinutes(editData.reminderMinutes ?? DEFAULT_REMINDER_MINUTES[resolvedType])\n setVisibility(editData.visibility ?? 'team')\n setParticipants(\n Array.isArray(editData.participants)\n ? editData.participants.map((p, i) => ({\n userId: p.userId,\n name: p.name ?? p.userId,\n email: p.email,\n color: PARTICIPANT_COLORS[i % PARTICIPANT_COLORS.length],\n status: (p.status ?? 'pending') as RsvpStatus,\n }))\n : [],\n )\n setLinkedEntities(\n Array.isArray(editData.linkedEntities)\n ? editData.linkedEntities.map((e) => ({ id: e.id, type: e.type as LinkedEntity['type'], label: e.label }))\n : [],\n )\n if (editData.recurrenceRule) {\n setRecurrenceEnabled(true)\n // Parse RRULE to set days and end type\n const rule = editData.recurrenceRule\n const byDayMatch = rule.match(/BYDAY=([A-Z,]+)/)\n if (byDayMatch) {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const activeDays = byDayMatch[1].split(',')\n setRecurrenceDays(dayNames.map((d) => activeDays.includes(d)))\n }\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const untilMatch = rule.match(/UNTIL=(\\d{8})/)\n if (countMatch) {\n setRecurrenceEndType('count')\n setRecurrenceCount(Number(countMatch[1]))\n } else if (untilMatch) {\n setRecurrenceEndType('date')\n const raw = untilMatch[1]\n setRecurrenceEndDate(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`)\n } else {\n setRecurrenceEndType('never')\n }\n } else {\n setRecurrenceEnabled(false)\n }\n if (editData.guestPermissions) {\n setGuestPermissions({\n canInviteOthers: editData.guestPermissions.canInviteOthers ?? true,\n canModify: editData.guestPermissions.canModify ?? false,\n canSeeList: editData.guestPermissions.canSeeList ?? true,\n })\n }\n } else {\n // Create mode: reset all fields\n setActivityType('meeting')\n setTitle('')\n setDate(format(new Date(), 'yyyy-MM-dd'))\n setStartTime('10:00')\n setDuration(30)\n setAllDay(false)\n setDescription('')\n setLocation('')\n setReminderMinutes(DEFAULT_REMINDER_MINUTES.meeting)\n setVisibility('team')\n setParticipants([])\n setLinkedEntities([])\n setRecurrenceEnabled(false)\n }\n setConflict(null)\n }\n return () => {\n // Safety net: restore body scroll if Radix Dialog fails to clean up\n document.body.style.removeProperty('overflow')\n document.body.style.removeProperty('pointer-events')\n }\n }, [open, editData])\n\n // Update the Reminder default when the activity type changes in create mode.\n // Skipped in edit mode (the persisted value wins), and gated by `open` to\n // avoid flipping the default in a closed-but-mounted dialog.\n const lastReminderTypeRef = React.useRef<ActivityType>('meeting')\n React.useEffect(() => {\n if (!open || editData) {\n lastReminderTypeRef.current = activityType\n return\n }\n if (lastReminderTypeRef.current === activityType) return\n lastReminderTypeRef.current = activityType\n setReminderMinutes(DEFAULT_REMINDER_MINUTES[activityType])\n }, [activityType, editData, open])\n\n const removeParticipant = React.useCallback((userId: string) => {\n setParticipants((prev) => prev.filter((p) => p.userId !== userId))\n }, [])\n\n const toggleRecurrenceDay = React.useCallback((index: number) => {\n setRecurrenceDays((prev) => {\n const next = [...prev]\n next[index] = !next[index]\n return next\n })\n }, [])\n\n return {\n activityType,\n setActivityType,\n title,\n setTitle,\n date,\n setDate,\n startTime,\n setStartTime,\n duration,\n setDuration,\n allDay,\n setAllDay,\n description,\n setDescription,\n markdownEnabled,\n setMarkdownEnabled,\n location,\n setLocation,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n participants,\n setParticipants,\n linkedEntities,\n setLinkedEntities,\n recurrenceEnabled,\n setRecurrenceEnabled,\n recurrenceDays,\n setRecurrenceDays,\n recurrenceEndType,\n setRecurrenceEndType,\n recurrenceCount,\n setRecurrenceCount,\n recurrenceEndDate,\n setRecurrenceEndDate,\n conflict,\n setConflict,\n saving,\n setSaving,\n guestPermissions,\n setGuestPermissions,\n removeParticipant,\n toggleRecurrenceDay,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n /**\n * Historical timestamp for completed activities (status `done`). Required for\n * the edit prefill to restore the original date/time instead of falling back\n * to \"today\" (#1807).\n */\n occurredAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\n// Per-Figma defaults for the Reminder dropdown when the user picks an activity\n// type. Meeting/email keep the standard 15 min; tasks default to 1 day (1440 min)\n// because they're plan-ahead artefacts; calls default to 5 min as a stand-in for\n// the Figma \"After call ends\" treatment (which would need a non-numeric sentinel\n// in the API contract \u2014 tracked as a follow-up).\nconst DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {\n meeting: 15,\n call: 5,\n task: 1440,\n email: 15,\n}\n\nfunction padDatePart(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nfunction formatLocalDateInput(date: Date): string {\n return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`\n}\n\nfunction formatLocalTimeInput(date: Date): string {\n return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`\n}\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => formatLocalDateInput(new Date()))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n const resolvedType = (editData.interactionType as ActivityType) ?? 'meeting'\n setActivityType(resolvedType)\n setTitle(editData.title ?? '')\n // For historical activities the canonical timestamp is `occurredAt`; for\n // planned/future ones it's `scheduledAt`. Without this fallback editing a\n // past activity prefilled to \"today\" instead of its actual moment (#1807).\n // Keep seed values in the user's local timezone, matching the cluster-E\n // local-day convention.\n const sourceTimestamp = editData.occurredAt ?? editData.scheduledAt ?? null\n const seedDate = sourceTimestamp ? new Date(sourceTimestamp) : new Date()\n const seedDateValid = !Number.isNaN(seedDate.getTime())\n const fallbackNow = new Date()\n const dateForForm = seedDateValid ? seedDate : fallbackNow\n setDate(formatLocalDateInput(dateForForm))\n setStartTime(formatLocalTimeInput(dateForForm))\n setDuration(editData.durationMinutes ?? 30)\n setAllDay(editData.allDay ?? false)\n setDescription(editData.body ?? '')\n setLocation(editData.location ?? '')\n // Use per-type default when the editData omits an explicit reminder\n // (the menu-driven \"New X\" flow opens the dialog with `reminderMinutes: null`).\n setReminderMinutes(editData.reminderMinutes ?? DEFAULT_REMINDER_MINUTES[resolvedType])\n setVisibility(editData.visibility ?? 'team')\n setParticipants(\n Array.isArray(editData.participants)\n ? editData.participants.map((p, i) => ({\n userId: p.userId,\n name: p.name ?? p.userId,\n email: p.email,\n color: PARTICIPANT_COLORS[i % PARTICIPANT_COLORS.length],\n status: (p.status ?? 'pending') as RsvpStatus,\n }))\n : [],\n )\n setLinkedEntities(\n Array.isArray(editData.linkedEntities)\n ? editData.linkedEntities.map((e) => ({ id: e.id, type: e.type as LinkedEntity['type'], label: e.label }))\n : [],\n )\n if (editData.recurrenceRule) {\n setRecurrenceEnabled(true)\n // Parse RRULE to set days and end type\n const rule = editData.recurrenceRule\n const byDayMatch = rule.match(/BYDAY=([A-Z,]+)/)\n if (byDayMatch) {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const activeDays = byDayMatch[1].split(',')\n setRecurrenceDays(dayNames.map((d) => activeDays.includes(d)))\n }\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const untilMatch = rule.match(/UNTIL=(\\d{8})/)\n if (countMatch) {\n setRecurrenceEndType('count')\n setRecurrenceCount(Number(countMatch[1]))\n } else if (untilMatch) {\n setRecurrenceEndType('date')\n const raw = untilMatch[1]\n setRecurrenceEndDate(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`)\n } else {\n setRecurrenceEndType('never')\n }\n } else {\n setRecurrenceEnabled(false)\n }\n if (editData.guestPermissions) {\n setGuestPermissions({\n canInviteOthers: editData.guestPermissions.canInviteOthers ?? true,\n canModify: editData.guestPermissions.canModify ?? false,\n canSeeList: editData.guestPermissions.canSeeList ?? true,\n })\n }\n } else {\n // Create mode: reset all fields\n setActivityType('meeting')\n setTitle('')\n setDate(formatLocalDateInput(new Date()))\n setStartTime('10:00')\n setDuration(30)\n setAllDay(false)\n setDescription('')\n setLocation('')\n setReminderMinutes(DEFAULT_REMINDER_MINUTES.meeting)\n setVisibility('team')\n setParticipants([])\n setLinkedEntities([])\n setRecurrenceEnabled(false)\n }\n setConflict(null)\n }\n return () => {\n // Safety net: restore body scroll if Radix Dialog fails to clean up\n document.body.style.removeProperty('overflow')\n document.body.style.removeProperty('pointer-events')\n }\n }, [open, editData])\n\n // Update the Reminder default when the activity type changes in create mode.\n // Skipped in edit mode (the persisted value wins), and gated by `open` to\n // avoid flipping the default in a closed-but-mounted dialog.\n const lastReminderTypeRef = React.useRef<ActivityType>('meeting')\n React.useEffect(() => {\n if (!open || editData) {\n lastReminderTypeRef.current = activityType\n return\n }\n if (lastReminderTypeRef.current === activityType) return\n lastReminderTypeRef.current = activityType\n setReminderMinutes(DEFAULT_REMINDER_MINUTES[activityType])\n }, [activityType, editData, open])\n\n const removeParticipant = React.useCallback((userId: string) => {\n setParticipants((prev) => prev.filter((p) => p.userId !== userId))\n }, [])\n\n const toggleRecurrenceDay = React.useCallback((index: number) => {\n setRecurrenceDays((prev) => {\n const next = [...prev]\n next[index] = !next[index]\n return next\n })\n }, [])\n\n return {\n activityType,\n setActivityType,\n title,\n setTitle,\n date,\n setDate,\n startTime,\n setStartTime,\n duration,\n setDuration,\n allDay,\n setAllDay,\n description,\n setDescription,\n markdownEnabled,\n setMarkdownEnabled,\n location,\n setLocation,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n participants,\n setParticipants,\n linkedEntities,\n setLinkedEntities,\n recurrenceEnabled,\n setRecurrenceEnabled,\n recurrenceDays,\n setRecurrenceDays,\n recurrenceEndType,\n setRecurrenceEndType,\n recurrenceCount,\n setRecurrenceCount,\n recurrenceEndDate,\n setRecurrenceEndDate,\n conflict,\n setConflict,\n saving,\n setSaving,\n guestPermissions,\n setGuestPermissions,\n removeParticipant,\n toggleRecurrenceDay,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -26,12 +26,15 @@ const listIntegrationLogsQuerySchema = z.object({
26
26
  page: z.coerce.number().int().min(1).default(1),
27
27
  pageSize: z.coerce.number().int().min(1).max(100).default(20)
28
28
  });
29
- const optionalBooleanQuery = z.union([z.boolean(), z.string(), z.null(), z.undefined()]).transform((value) => {
30
- if (value === void 0 || value === "" || value === null) return void 0;
31
- if (value === true || value === "true" || value === "1") return true;
32
- if (value === false || value === "false" || value === "0") return false;
33
- return value;
34
- }).pipe(z.boolean().optional());
29
+ const optionalBooleanQuery = z.preprocess(
30
+ (value) => {
31
+ if (value === void 0 || value === "" || value === null) return void 0;
32
+ if (value === true || value === "true" || value === "1") return true;
33
+ if (value === false || value === "false" || value === "0") return false;
34
+ return value;
35
+ },
36
+ z.boolean().optional()
37
+ );
35
38
  const integrationMarketplaceHealthStatusSchema = z.enum(["healthy", "degraded", "unhealthy", "unconfigured"]);
36
39
  const listIntegrationsQuerySchema = z.object({
37
40
  q: z.string().max(200).optional(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\n\nexport const saveCredentialsSchema = z.object({\n credentials: z.record(\n z.string().min(1).max(128),\n z.union([z.string().max(20_000), z.number(), z.boolean(), z.null()]),\n ),\n}).refine((value) => Object.keys(value.credentials).length <= 200, {\n message: 'At most 200 credential fields are allowed',\n})\n\nexport type SaveCredentialsInput = z.infer<typeof saveCredentialsSchema>\n\nexport const updateVersionSchema = z.object({\n apiVersion: z.string().min(1),\n})\n\nexport type UpdateVersionInput = z.infer<typeof updateVersionSchema>\n\nexport const updateStateSchema = z.object({\n isEnabled: z.boolean().optional(),\n reauthRequired: z.boolean().optional(),\n}).refine((value) => value.isEnabled !== undefined || value.reauthRequired !== undefined, {\n message: 'At least one state field must be provided',\n})\n\nexport type UpdateStateInput = z.infer<typeof updateStateSchema>\n\nexport const integrationLogLevelSchema = z.enum(['info', 'warn', 'error'])\n\nexport const listIntegrationLogsQuerySchema = z.object({\n integrationId: z.string().min(1).optional(),\n level: integrationLogLevelSchema.optional(),\n runId: z.string().uuid().optional(),\n entityType: z.string().optional(),\n entityId: z.string().uuid().optional(),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\nexport type ListIntegrationLogsQuery = z.infer<typeof listIntegrationLogsQuerySchema>\n\nconst optionalBooleanQuery = z.union([z.boolean(), z.string(), z.null(), z.undefined()])\n .transform((value) => {\n if (value === undefined || value === '' || value === null) return undefined\n if (value === true || value === 'true' || value === '1') return true\n if (value === false || value === 'false' || value === '0') return false\n return value\n })\n .pipe(z.boolean().optional())\n\nexport const integrationMarketplaceHealthStatusSchema = z.enum(['healthy', 'degraded', 'unhealthy', 'unconfigured'])\n\nexport const listIntegrationsQuerySchema = z.object({\n q: z.string().max(200).optional(),\n category: z.string().max(64).optional(),\n bundleId: z.string().max(128).optional(),\n isEnabled: optionalBooleanQuery,\n healthStatus: integrationMarketplaceHealthStatusSchema.optional(),\n sort: z.enum(['title', 'category', 'enabledAt', 'healthStatus']).optional(),\n order: z.enum(['asc', 'desc']).default('asc'),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(100),\n})\n\nexport type ListIntegrationsQuery = z.infer<typeof listIntegrationsQuerySchema>\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,aAAa,EAAE;AAAA,IACb,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,IACzB,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAM,GAAG,EAAE,OAAO,GAAG,EAAE,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;AAAA,EACrE;AACF,CAAC,EAAE,OAAO,CAAC,UAAU,OAAO,KAAK,MAAM,WAAW,EAAE,UAAU,KAAK;AAAA,EACjE,SAAS;AACX,CAAC;AAIM,MAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAC9B,CAAC;AAIM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,EAChC,gBAAgB,EAAE,QAAQ,EAAE,SAAS;AACvC,CAAC,EAAE,OAAO,CAAC,UAAU,MAAM,cAAc,UAAa,MAAM,mBAAmB,QAAW;AAAA,EACxF,SAAS;AACX,CAAC;AAIM,MAAM,4BAA4B,EAAE,KAAK,CAAC,QAAQ,QAAQ,OAAO,CAAC;AAElE,MAAM,iCAAiC,EAAE,OAAO;AAAA,EACrD,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC1C,OAAO,0BAA0B,SAAS;AAAA,EAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACrC,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAC9D,CAAC;AAID,MAAM,uBAAuB,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,EAAE,OAAO,GAAG,EAAE,KAAK,GAAG,EAAE,UAAU,CAAC,CAAC,EACpF,UAAU,CAAC,UAAU;AACpB,MAAI,UAAU,UAAa,UAAU,MAAM,UAAU,KAAM,QAAO;AAClE,MAAI,UAAU,QAAQ,UAAU,UAAU,UAAU,IAAK,QAAO;AAChE,MAAI,UAAU,SAAS,UAAU,WAAW,UAAU,IAAK,QAAO;AAClE,SAAO;AACT,CAAC,EACA,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC;AAEvB,MAAM,2CAA2C,EAAE,KAAK,CAAC,WAAW,YAAY,aAAa,cAAc,CAAC;AAE5G,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAClD,GAAG,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACvC,WAAW;AAAA,EACX,cAAc,yCAAyC,SAAS;AAAA,EAChE,MAAM,EAAE,KAAK,CAAC,SAAS,YAAY,aAAa,cAAc,CAAC,EAAE,SAAS;AAAA,EAC1E,OAAO,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,QAAQ,KAAK;AAAA,EAC5C,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,GAAG;AAC/D,CAAC;",
4
+ "sourcesContent": ["import { z } from 'zod'\n\nexport const saveCredentialsSchema = z.object({\n credentials: z.record(\n z.string().min(1).max(128),\n z.union([z.string().max(20_000), z.number(), z.boolean(), z.null()]),\n ),\n}).refine((value) => Object.keys(value.credentials).length <= 200, {\n message: 'At most 200 credential fields are allowed',\n})\n\nexport type SaveCredentialsInput = z.infer<typeof saveCredentialsSchema>\n\nexport const updateVersionSchema = z.object({\n apiVersion: z.string().min(1),\n})\n\nexport type UpdateVersionInput = z.infer<typeof updateVersionSchema>\n\nexport const updateStateSchema = z.object({\n isEnabled: z.boolean().optional(),\n reauthRequired: z.boolean().optional(),\n}).refine((value) => value.isEnabled !== undefined || value.reauthRequired !== undefined, {\n message: 'At least one state field must be provided',\n})\n\nexport type UpdateStateInput = z.infer<typeof updateStateSchema>\n\nexport const integrationLogLevelSchema = z.enum(['info', 'warn', 'error'])\n\nexport const listIntegrationLogsQuerySchema = z.object({\n integrationId: z.string().min(1).optional(),\n level: integrationLogLevelSchema.optional(),\n runId: z.string().uuid().optional(),\n entityType: z.string().optional(),\n entityId: z.string().uuid().optional(),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n})\n\nexport type ListIntegrationLogsQuery = z.infer<typeof listIntegrationLogsQuerySchema>\n\nconst optionalBooleanQuery = z.preprocess(\n (value) => {\n if (value === undefined || value === '' || value === null) return undefined\n if (value === true || value === 'true' || value === '1') return true\n if (value === false || value === 'false' || value === '0') return false\n return value\n },\n z.boolean().optional(),\n)\n\nexport const integrationMarketplaceHealthStatusSchema = z.enum(['healthy', 'degraded', 'unhealthy', 'unconfigured'])\n\nexport const listIntegrationsQuerySchema = z.object({\n q: z.string().max(200).optional(),\n category: z.string().max(64).optional(),\n bundleId: z.string().max(128).optional(),\n isEnabled: optionalBooleanQuery,\n healthStatus: integrationMarketplaceHealthStatusSchema.optional(),\n sort: z.enum(['title', 'category', 'enabledAt', 'healthStatus']).optional(),\n order: z.enum(['asc', 'desc']).default('asc'),\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(100),\n})\n\nexport type ListIntegrationsQuery = z.infer<typeof listIntegrationsQuerySchema>\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,aAAa,EAAE;AAAA,IACb,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,IACzB,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAM,GAAG,EAAE,OAAO,GAAG,EAAE,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;AAAA,EACrE;AACF,CAAC,EAAE,OAAO,CAAC,UAAU,OAAO,KAAK,MAAM,WAAW,EAAE,UAAU,KAAK;AAAA,EACjE,SAAS;AACX,CAAC;AAIM,MAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAC9B,CAAC;AAIM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,EAChC,gBAAgB,EAAE,QAAQ,EAAE,SAAS;AACvC,CAAC,EAAE,OAAO,CAAC,UAAU,MAAM,cAAc,UAAa,MAAM,mBAAmB,QAAW;AAAA,EACxF,SAAS;AACX,CAAC;AAIM,MAAM,4BAA4B,EAAE,KAAK,CAAC,QAAQ,QAAQ,OAAO,CAAC;AAElE,MAAM,iCAAiC,EAAE,OAAO;AAAA,EACrD,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAC1C,OAAO,0BAA0B,SAAS;AAAA,EAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACrC,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAC9D,CAAC;AAID,MAAM,uBAAuB,EAAE;AAAA,EAC7B,CAAC,UAAU;AACT,QAAI,UAAU,UAAa,UAAU,MAAM,UAAU,KAAM,QAAO;AAClE,QAAI,UAAU,QAAQ,UAAU,UAAU,UAAU,IAAK,QAAO;AAChE,QAAI,UAAU,SAAS,UAAU,WAAW,UAAU,IAAK,QAAO;AAClE,WAAO;AAAA,EACT;AAAA,EACA,EAAE,QAAQ,EAAE,SAAS;AACvB;AAEO,MAAM,2CAA2C,EAAE,KAAK,CAAC,WAAW,YAAY,aAAa,cAAc,CAAC;AAE5G,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAClD,GAAG,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACvC,WAAW;AAAA,EACX,cAAc,yCAAyC,SAAS;AAAA,EAChE,MAAM,EAAE,KAAK,CAAC,SAAS,YAAY,aAAa,cAAc,CAAC,EAAE,SAAS;AAAA,EAC1E,OAAO,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,QAAQ,KAAK;AAAA,EAC5C,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EAC9C,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,GAAG;AAC/D,CAAC;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { registerCommand } from "@open-mercato/shared/lib/commands";
3
3
  import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
4
- import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
5
  import { Message, MessageObject, MessageRecipient } from "../data/entities.js";
6
6
  import { emitMessagesEvent } from "../events.js";
7
7
  import {
@@ -282,6 +282,17 @@ const updateDraftCommand = {
282
282
  const message = await requireMessageById(em, input, input.messageId);
283
283
  if (message.senderUserId !== input.userId) throw new Error("Access denied");
284
284
  if (!message.isDraft) throw new Error("Only draft messages can be edited");
285
+ const isSending = input.isDraft === false;
286
+ const preloadedRecipients = isSending && !input.recipients ? await findWithDecryption(
287
+ em,
288
+ MessageRecipient,
289
+ { messageId: message.id, deletedAt: null },
290
+ void 0,
291
+ {
292
+ tenantId: input.tenantId,
293
+ organizationId: input.organizationId
294
+ }
295
+ ) : null;
285
296
  const nextMessageType = input.type ?? message.type;
286
297
  if (input.objects) {
287
298
  const objectValidationError = validateMessageObjectsForType(nextMessageType, input.objects);
@@ -363,12 +374,41 @@ const updateDraftCommand = {
363
374
  input.tenantId
364
375
  );
365
376
  }
377
+ if (isSending) {
378
+ const finalVisibility = input.visibility ?? message.visibility;
379
+ const finalSubject = input.subject ?? message.subject;
380
+ const finalBody = input.body ?? message.body;
381
+ const finalRecipientCount = input.recipients ? input.recipients.length : preloadedRecipients?.length ?? 0;
382
+ if (finalVisibility !== "public" && finalRecipientCount === 0) {
383
+ throw new Error("at least one recipient is required");
384
+ }
385
+ if (!finalSubject?.trim()) throw new Error("subject is required");
386
+ if (!finalBody?.trim()) throw new Error("body is required");
387
+ message.isDraft = false;
388
+ message.status = "sent";
389
+ message.sentAt = /* @__PURE__ */ new Date();
390
+ if (!message.threadId) message.threadId = message.id;
391
+ }
366
392
  await em.flush();
367
393
  await emitMessageIndexUpsert(ctx.container, {
368
394
  messageId: message.id,
369
395
  tenantId: input.tenantId,
370
396
  organizationId: input.organizationId
371
397
  });
398
+ if (isSending) {
399
+ const recipientUserIds = input.recipients ? input.recipients.map((r) => r.userId) : (preloadedRecipients ?? []).map((r) => r.recipientUserId);
400
+ const resolvedVisibility = input.visibility ?? message.visibility;
401
+ const resolvedSendViaEmail = input.sendViaEmail ?? message.sendViaEmail;
402
+ await emitMessageSentEvent(ctx.container, {
403
+ messageId: message.id,
404
+ senderUserId: input.userId,
405
+ recipientUserIds,
406
+ sendViaEmail: resolvedVisibility === "public" ? true : resolvedSendViaEmail,
407
+ externalEmail: message.externalEmail ?? null,
408
+ tenantId: input.tenantId,
409
+ organizationId: input.organizationId
410
+ });
411
+ }
372
412
  return { ok: true, id: message.id };
373
413
  },
374
414
  async captureAfter(rawInput, _result, ctx) {
@@ -382,7 +422,7 @@ const updateDraftCommand = {
382
422
  buildLog: async ({ input, snapshots }) => {
383
423
  const parsed = updateDraftCommandSchema.parse(input);
384
424
  return {
385
- actionLabel: "Update draft message",
425
+ actionLabel: parsed.isDraft === false ? "Send draft message" : "Update draft message",
386
426
  resourceKind: "messages.message",
387
427
  resourceId: parsed.messageId,
388
428
  tenantId: parsed.tenantId,