@open-mercato/core 0.6.6-develop.6286.1.d985c5c374 → 0.6.6-develop.6296.1.5e8bdfee17

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 (26) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/catalog/components/products/ProductComplianceSection.js +235 -62
  3. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +2 -2
  4. package/dist/modules/configs/components/CachePanel.js +2 -1
  5. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  6. package/dist/modules/configs/components/SystemStatusPanel.js +10 -12
  7. package/dist/modules/configs/components/SystemStatusPanel.js.map +2 -2
  8. package/dist/modules/data_sync/workers/sync-scheduled.js +13 -14
  9. package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
  10. package/dist/modules/directory/components/TenantSelect.js +29 -16
  11. package/dist/modules/directory/components/TenantSelect.js.map +2 -2
  12. package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js +47 -1
  13. package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js.map +2 -2
  14. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/hiddenWidgetsStorage.js +35 -0
  15. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/hiddenWidgetsStorage.js.map +7 -0
  16. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +18 -19
  17. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  18. package/package.json +7 -7
  19. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +138 -27
  20. package/src/modules/configs/components/CachePanel.tsx +2 -3
  21. package/src/modules/configs/components/SystemStatusPanel.tsx +12 -19
  22. package/src/modules/data_sync/workers/sync-scheduled.ts +13 -14
  23. package/src/modules/directory/components/TenantSelect.tsx +33 -16
  24. package/src/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.ts +54 -1
  25. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/hiddenWidgetsStorage.ts +36 -0
  26. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +22 -23
@@ -47,21 +47,10 @@ function filterTenantsByStatus(list, status) {
47
47
  if (status === "inactive") return list.filter((tenant) => !tenant.isActive);
48
48
  return list;
49
49
  }
50
- async function fetchDirectoryTenants(status, errorMessage) {
51
- const search = new URLSearchParams();
52
- search.set("page", "1");
53
- search.set("pageSize", "100");
54
- search.set("sortField", "name");
55
- search.set("sortDir", "asc");
56
- if (status === "active") search.set("isActive", "true");
57
- if (status === "inactive") search.set("isActive", "false");
58
- const json = await readApiResultOrThrow(
59
- `/api/directory/tenants?${search.toString()}`,
60
- void 0,
61
- { errorMessage, allowNullResult: true }
62
- );
63
- const items = Array.isArray(json?.items) ? json.items : [];
64
- const normalized = items.map((item) => {
50
+ const DIRECTORY_TENANTS_PAGE_SIZE = 100;
51
+ const DIRECTORY_TENANTS_MAX_PAGES = 200;
52
+ function normalizeTenantItems(items) {
53
+ return items.map((item) => {
65
54
  if (!item || typeof item !== "object") return null;
66
55
  const entry = item;
67
56
  const rawId = entry.id;
@@ -72,7 +61,31 @@ async function fetchDirectoryTenants(status, errorMessage) {
72
61
  const isActive = entry.isActive !== false;
73
62
  return { id, name, isActive };
74
63
  }).filter((tenant) => tenant !== null);
75
- return mergeTenantLists(filterTenantsByStatus(normalized, status));
64
+ }
65
+ async function fetchDirectoryTenants(status, errorMessage) {
66
+ const collected = [];
67
+ let page = 1;
68
+ let totalPages = 1;
69
+ do {
70
+ const search = new URLSearchParams();
71
+ search.set("page", String(page));
72
+ search.set("pageSize", String(DIRECTORY_TENANTS_PAGE_SIZE));
73
+ search.set("sortField", "name");
74
+ search.set("sortDir", "asc");
75
+ if (status === "active") search.set("isActive", "true");
76
+ if (status === "inactive") search.set("isActive", "false");
77
+ const json = await readApiResultOrThrow(
78
+ `/api/directory/tenants?${search.toString()}`,
79
+ void 0,
80
+ { errorMessage, allowNullResult: true }
81
+ );
82
+ const items = Array.isArray(json?.items) ? json.items : [];
83
+ collected.push(...normalizeTenantItems(items));
84
+ totalPages = typeof json?.totalPages === "number" && Number.isFinite(json.totalPages) ? Math.max(1, Math.floor(json.totalPages)) : 1;
85
+ if (items.length < DIRECTORY_TENANTS_PAGE_SIZE) break;
86
+ page += 1;
87
+ } while (page <= totalPages && page <= DIRECTORY_TENANTS_MAX_PAGES);
88
+ return mergeTenantLists(filterTenantsByStatus(collected, status));
76
89
  }
77
90
  async function fetchTenantsFromOrganizationSwitcher(status, errorMessage) {
78
91
  const payload = await readApiResultOrThrow(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/directory/components/TenantSelect.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type TenantRecord = {\n id: string\n name: string\n isActive: boolean\n}\n\nfunction normalizeTenant(tenant: TenantRecord | null | undefined): TenantRecord | null {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) return null\n const id = tenant.id.trim()\n const name = typeof tenant.name === 'string' && tenant.name.trim().length > 0 ? tenant.name.trim() : id\n const isActive = tenant.isActive !== false\n return { id, name, isActive }\n}\n\nfunction sanitizeTenantList(list?: TenantRecord[] | null): TenantRecord[] {\n if (!Array.isArray(list)) return []\n const seen = new Map<string, TenantRecord>()\n for (const tenant of list) {\n const normalized = normalizeTenant(tenant)\n if (!normalized) continue\n seen.set(normalized.id, normalized)\n }\n return Array.from(seen.values())\n}\n\nfunction mergeTenantLists(...lists: TenantRecord[][]): TenantRecord[] {\n const map = new Map<string, TenantRecord>()\n for (const list of lists) {\n for (const tenant of list) {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) continue\n const id = tenant.id.trim()\n const existing = map.get(id)\n if (!existing) {\n map.set(id, {\n id,\n name: typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : id,\n isActive: tenant.isActive !== false,\n })\n } else {\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : existing.name\n const isActive = tenant.isActive !== undefined ? tenant.isActive !== false : existing.isActive\n map.set(id, { id, name, isActive })\n }\n }\n }\n return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name))\n}\n\nfunction filterTenantsByStatus(list: TenantRecord[], status: 'all' | 'active' | 'inactive'): TenantRecord[] {\n if (status === 'active') return list.filter((tenant: TenantRecord) => tenant.isActive)\n if (status === 'inactive') return list.filter((tenant: TenantRecord) => !tenant.isActive)\n return list\n}\n\nasync function fetchDirectoryTenants(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {\n const search = new URLSearchParams()\n search.set('page', '1')\n search.set('pageSize', '100')\n search.set('sortField', 'name')\n search.set('sortDir', 'asc')\n if (status === 'active') search.set('isActive', 'true')\n if (status === 'inactive') search.set('isActive', 'false')\n const json = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/directory/tenants?${search.toString()}`,\n undefined,\n { errorMessage, allowNullResult: true },\n )\n const items = Array.isArray(json?.items) ? json.items : []\n const normalized = items\n .map((item: unknown): TenantRecord | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const rawId = entry.id\n const id = typeof rawId === 'string' ? rawId : null\n if (!id || !id.length) return null\n const rawName = entry.name\n const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : id\n const isActive = entry.isActive !== false\n return { id, name, isActive }\n })\n .filter((tenant: TenantRecord | null): tenant is TenantRecord => tenant !== null)\n return mergeTenantLists(filterTenantsByStatus(normalized, status))\n}\n\nasync function fetchTenantsFromOrganizationSwitcher(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n '/api/directory/organization-switcher',\n undefined,\n { errorMessage, allowNullResult: true },\n )\n const json = payload ?? {}\n const rawTenants = Array.isArray(json.tenants) ? json.tenants : []\n const sanitized = sanitizeTenantList(rawTenants as TenantRecord[] | null)\n return mergeTenantLists(filterTenantsByStatus(sanitized, status))\n}\n\nexport type TenantSelectProps = {\n value?: string | null\n onChange?: (value: string | null) => void\n disabled?: boolean\n required?: boolean\n className?: string\n id?: string\n name?: string\n includeEmptyOption?: boolean\n emptyOptionLabel?: string\n fetchOnMount?: boolean\n status?: 'all' | 'active' | 'inactive'\n tenants?: TenantRecord[] | null\n}\n\nexport const TenantSelect = React.forwardRef<HTMLSelectElement, TenantSelectProps>(function TenantSelect(\n {\n value,\n onChange,\n disabled = false,\n required = false,\n className,\n id,\n name,\n includeEmptyOption = false,\n emptyOptionLabel,\n fetchOnMount = true,\n status = 'all',\n tenants: providedTenantsInput = null,\n },\n ref,\n) {\n const t = useT()\n const providedTenants = React.useMemo(() => sanitizeTenantList(providedTenantsInput), [providedTenantsInput])\n const [remoteTenants, setRemoteTenants] = React.useState<TenantRecord[]>([])\n const [fetchStatus, setFetchStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>(fetchOnMount ? 'loading' : 'idle')\n\n React.useEffect(() => {\n const fetchErrorMessage = t('tenantSelect.error', 'Failed to load tenants')\n if (!fetchOnMount) {\n setFetchStatus((prev) => (prev === 'loading' ? 'idle' : prev))\n return\n }\n let cancelled = false\n const loadTenants = async () => {\n setFetchStatus('loading')\n const autoSelect = (tenants: TenantRecord[]) => {\n if (!value && !includeEmptyOption && tenants.length > 0) {\n onChange?.(tenants[0].id)\n }\n }\n try {\n const tenants = await fetchDirectoryTenants(status, fetchErrorMessage)\n if (cancelled) return\n setRemoteTenants(tenants)\n autoSelect(tenants)\n setFetchStatus('success')\n return\n } catch {\n try {\n const fallbackTenants = await fetchTenantsFromOrganizationSwitcher(status, fetchErrorMessage)\n if (cancelled) return\n setRemoteTenants(fallbackTenants)\n autoSelect(fallbackTenants)\n setFetchStatus('success')\n return\n } catch {\n if (cancelled) return\n setFetchStatus('error')\n }\n }\n }\n void loadTenants()\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- value/onChange/includeEmptyOption are read once at fetch time for auto-select\n }, [fetchOnMount, status, t])\n\n const mergedTenants = React.useMemo(\n () => mergeTenantLists(providedTenants, remoteTenants),\n [providedTenants, remoteTenants],\n )\n\n const fallbackTenant = React.useMemo(() => {\n const tenantId = typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n if (!tenantId) return null\n if (mergedTenants.some((tenant) => tenant.id === tenantId)) return null\n return { id: tenantId, name: tenantId, isActive: true }\n }, [mergedTenants, value])\n\n const tenantsForRender = React.useMemo(\n () => (fallbackTenant ? mergeTenantLists(mergedTenants, [fallbackTenant]) : mergedTenants),\n [mergedTenants, fallbackTenant],\n )\n\n const showLoading = fetchStatus === 'loading' && tenantsForRender.length === 0\n const showError = fetchStatus === 'error' && tenantsForRender.length === 0\n\n const handleChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {\n if (!onChange) return\n const next = event.target.value\n onChange(next ? next : null)\n }, [onChange])\n\n const selectValue = value ?? ''\n const resolvedEmptyOptionLabel = emptyOptionLabel ?? t('tenantSelect.empty')\n const loadingLabel = t('tenantSelect.loading')\n const errorLabel = t('tenantSelect.error')\n const inactiveSuffix = ` (${t('tenantSelect.inactive')})`\n\n return (\n <select\n ref={ref}\n id={id}\n name={name}\n value={selectValue}\n onChange={handleChange}\n disabled={disabled || (showError && tenantsForRender.length === 0)}\n required={required}\n className={className}\n >\n {includeEmptyOption ? (\n <option value=\"\">\n {resolvedEmptyOptionLabel}\n </option>\n ) : null}\n {showLoading ? (\n <option value=\"\" disabled>{loadingLabel}</option>\n ) : null}\n {showError ? (\n <option value=\"\" disabled>{errorLabel}</option>\n ) : null}\n {!showLoading && tenantsForRender.length > 0\n ? tenantsForRender.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name}{tenant.isActive ? '' : inactiveSuffix}\n </option>\n ))\n : null}\n </select>\n )\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type TenantRecord = {\n id: string\n name: string\n isActive: boolean\n}\n\nfunction normalizeTenant(tenant: TenantRecord | null | undefined): TenantRecord | null {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) return null\n const id = tenant.id.trim()\n const name = typeof tenant.name === 'string' && tenant.name.trim().length > 0 ? tenant.name.trim() : id\n const isActive = tenant.isActive !== false\n return { id, name, isActive }\n}\n\nfunction sanitizeTenantList(list?: TenantRecord[] | null): TenantRecord[] {\n if (!Array.isArray(list)) return []\n const seen = new Map<string, TenantRecord>()\n for (const tenant of list) {\n const normalized = normalizeTenant(tenant)\n if (!normalized) continue\n seen.set(normalized.id, normalized)\n }\n return Array.from(seen.values())\n}\n\nfunction mergeTenantLists(...lists: TenantRecord[][]): TenantRecord[] {\n const map = new Map<string, TenantRecord>()\n for (const list of lists) {\n for (const tenant of list) {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) continue\n const id = tenant.id.trim()\n const existing = map.get(id)\n if (!existing) {\n map.set(id, {\n id,\n name: typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : id,\n isActive: tenant.isActive !== false,\n })\n } else {\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : existing.name\n const isActive = tenant.isActive !== undefined ? tenant.isActive !== false : existing.isActive\n map.set(id, { id, name, isActive })\n }\n }\n }\n return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name))\n}\n\nfunction filterTenantsByStatus(list: TenantRecord[], status: 'all' | 'active' | 'inactive'): TenantRecord[] {\n if (status === 'active') return list.filter((tenant: TenantRecord) => tenant.isActive)\n if (status === 'inactive') return list.filter((tenant: TenantRecord) => !tenant.isActive)\n return list\n}\n\nconst DIRECTORY_TENANTS_PAGE_SIZE = 100\nconst DIRECTORY_TENANTS_MAX_PAGES = 200\n\nfunction normalizeTenantItems(items: unknown[]): TenantRecord[] {\n return items\n .map((item: unknown): TenantRecord | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const rawId = entry.id\n const id = typeof rawId === 'string' ? rawId : null\n if (!id || !id.length) return null\n const rawName = entry.name\n const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : id\n const isActive = entry.isActive !== false\n return { id, name, isActive }\n })\n .filter((tenant: TenantRecord | null): tenant is TenantRecord => tenant !== null)\n}\n\nasync function fetchDirectoryTenants(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {\n const collected: TenantRecord[] = []\n let page = 1\n let totalPages = 1\n do {\n const search = new URLSearchParams()\n search.set('page', String(page))\n search.set('pageSize', String(DIRECTORY_TENANTS_PAGE_SIZE))\n search.set('sortField', 'name')\n search.set('sortDir', 'asc')\n if (status === 'active') search.set('isActive', 'true')\n if (status === 'inactive') search.set('isActive', 'false')\n const json = await readApiResultOrThrow<{ items?: unknown[]; totalPages?: unknown }>(\n `/api/directory/tenants?${search.toString()}`,\n undefined,\n { errorMessage, allowNullResult: true },\n )\n const items = Array.isArray(json?.items) ? json.items : []\n collected.push(...normalizeTenantItems(items))\n totalPages = typeof json?.totalPages === 'number' && Number.isFinite(json.totalPages)\n ? Math.max(1, Math.floor(json.totalPages))\n : 1\n if (items.length < DIRECTORY_TENANTS_PAGE_SIZE) break\n page += 1\n } while (page <= totalPages && page <= DIRECTORY_TENANTS_MAX_PAGES)\n return mergeTenantLists(filterTenantsByStatus(collected, status))\n}\n\nasync function fetchTenantsFromOrganizationSwitcher(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n '/api/directory/organization-switcher',\n undefined,\n { errorMessage, allowNullResult: true },\n )\n const json = payload ?? {}\n const rawTenants = Array.isArray(json.tenants) ? json.tenants : []\n const sanitized = sanitizeTenantList(rawTenants as TenantRecord[] | null)\n return mergeTenantLists(filterTenantsByStatus(sanitized, status))\n}\n\nexport type TenantSelectProps = {\n value?: string | null\n onChange?: (value: string | null) => void\n disabled?: boolean\n required?: boolean\n className?: string\n id?: string\n name?: string\n includeEmptyOption?: boolean\n emptyOptionLabel?: string\n fetchOnMount?: boolean\n status?: 'all' | 'active' | 'inactive'\n tenants?: TenantRecord[] | null\n}\n\nexport const TenantSelect = React.forwardRef<HTMLSelectElement, TenantSelectProps>(function TenantSelect(\n {\n value,\n onChange,\n disabled = false,\n required = false,\n className,\n id,\n name,\n includeEmptyOption = false,\n emptyOptionLabel,\n fetchOnMount = true,\n status = 'all',\n tenants: providedTenantsInput = null,\n },\n ref,\n) {\n const t = useT()\n const providedTenants = React.useMemo(() => sanitizeTenantList(providedTenantsInput), [providedTenantsInput])\n const [remoteTenants, setRemoteTenants] = React.useState<TenantRecord[]>([])\n const [fetchStatus, setFetchStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>(fetchOnMount ? 'loading' : 'idle')\n\n React.useEffect(() => {\n const fetchErrorMessage = t('tenantSelect.error', 'Failed to load tenants')\n if (!fetchOnMount) {\n setFetchStatus((prev) => (prev === 'loading' ? 'idle' : prev))\n return\n }\n let cancelled = false\n const loadTenants = async () => {\n setFetchStatus('loading')\n const autoSelect = (tenants: TenantRecord[]) => {\n if (!value && !includeEmptyOption && tenants.length > 0) {\n onChange?.(tenants[0].id)\n }\n }\n try {\n const tenants = await fetchDirectoryTenants(status, fetchErrorMessage)\n if (cancelled) return\n setRemoteTenants(tenants)\n autoSelect(tenants)\n setFetchStatus('success')\n return\n } catch {\n try {\n const fallbackTenants = await fetchTenantsFromOrganizationSwitcher(status, fetchErrorMessage)\n if (cancelled) return\n setRemoteTenants(fallbackTenants)\n autoSelect(fallbackTenants)\n setFetchStatus('success')\n return\n } catch {\n if (cancelled) return\n setFetchStatus('error')\n }\n }\n }\n void loadTenants()\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- value/onChange/includeEmptyOption are read once at fetch time for auto-select\n }, [fetchOnMount, status, t])\n\n const mergedTenants = React.useMemo(\n () => mergeTenantLists(providedTenants, remoteTenants),\n [providedTenants, remoteTenants],\n )\n\n const fallbackTenant = React.useMemo(() => {\n const tenantId = typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n if (!tenantId) return null\n if (mergedTenants.some((tenant) => tenant.id === tenantId)) return null\n return { id: tenantId, name: tenantId, isActive: true }\n }, [mergedTenants, value])\n\n const tenantsForRender = React.useMemo(\n () => (fallbackTenant ? mergeTenantLists(mergedTenants, [fallbackTenant]) : mergedTenants),\n [mergedTenants, fallbackTenant],\n )\n\n const showLoading = fetchStatus === 'loading' && tenantsForRender.length === 0\n const showError = fetchStatus === 'error' && tenantsForRender.length === 0\n\n const handleChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {\n if (!onChange) return\n const next = event.target.value\n onChange(next ? next : null)\n }, [onChange])\n\n const selectValue = value ?? ''\n const resolvedEmptyOptionLabel = emptyOptionLabel ?? t('tenantSelect.empty')\n const loadingLabel = t('tenantSelect.loading')\n const errorLabel = t('tenantSelect.error')\n const inactiveSuffix = ` (${t('tenantSelect.inactive')})`\n\n return (\n <select\n ref={ref}\n id={id}\n name={name}\n value={selectValue}\n onChange={handleChange}\n disabled={disabled || (showError && tenantsForRender.length === 0)}\n required={required}\n className={className}\n >\n {includeEmptyOption ? (\n <option value=\"\">\n {resolvedEmptyOptionLabel}\n </option>\n ) : null}\n {showLoading ? (\n <option value=\"\" disabled>{loadingLabel}</option>\n ) : null}\n {showError ? (\n <option value=\"\" disabled>{errorLabel}</option>\n ) : null}\n {!showLoading && tenantsForRender.length > 0\n ? tenantsForRender.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name}{tenant.isActive ? '' : inactiveSuffix}\n </option>\n ))\n : null}\n </select>\n )\n})\n"],
5
+ "mappings": "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",
6
6
  "names": ["TenantSelect"]
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { NextResponse } from "next/server";
2
+ import { raw } from "@mikro-orm/core";
2
3
  import { Resend } from "resend";
3
4
  import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
4
5
  import { resolveDefaultEmailFromAddress } from "@open-mercato/shared/lib/email/config";
@@ -93,6 +94,50 @@ async function POST(req) {
93
94
  if (references && references.length > 0) {
94
95
  headers["References"] = references.join(" ");
95
96
  }
97
+ const sendClaimedAt = (/* @__PURE__ */ new Date()).toISOString();
98
+ const claimed = await ctx.em.nativeUpdate(
99
+ InboxProposalAction,
100
+ {
101
+ id: action.id,
102
+ proposalId: proposal.id,
103
+ actionType: "draft_reply",
104
+ organizationId: ctx.organizationId,
105
+ tenantId: ctx.tenantId,
106
+ deletedAt: null,
107
+ status: "executed",
108
+ [raw(`coalesce("metadata"->>'replySentAt', '')`)]: "",
109
+ [raw(`coalesce("metadata"->>'replySendClaimedAt', '')`)]: ""
110
+ },
111
+ {
112
+ metadata: raw(
113
+ `coalesce("metadata", '{}'::jsonb) || jsonb_build_object('replySendClaimedAt', ?::text)`,
114
+ [sendClaimedAt]
115
+ )
116
+ }
117
+ );
118
+ if (claimed === 0) {
119
+ return NextResponse.json(
120
+ { error: "Reply send is already in progress or has been sent for this action" },
121
+ { status: 409 }
122
+ );
123
+ }
124
+ const releaseSendClaim = async () => {
125
+ try {
126
+ await ctx.em.nativeUpdate(
127
+ InboxProposalAction,
128
+ {
129
+ id: action.id,
130
+ organizationId: ctx.organizationId,
131
+ tenantId: ctx.tenantId,
132
+ [raw(`coalesce("metadata"->>'replySentAt', '')`)]: "",
133
+ [raw(`("metadata"->>'replySendClaimedAt')`)]: sendClaimedAt
134
+ },
135
+ { metadata: raw(`"metadata" - 'replySendClaimedAt'`) }
136
+ );
137
+ } catch (releaseError) {
138
+ console.error("[inbox_ops:reply:send] Failed to release send claim:", releaseError);
139
+ }
140
+ };
96
141
  const resend = new Resend(apiKey);
97
142
  const { data: sendData, error: sendError } = await resend.emails.send({
98
143
  to: toAddress,
@@ -102,6 +147,7 @@ async function POST(req) {
102
147
  ...Object.keys(headers).length > 0 ? { headers } : {}
103
148
  });
104
149
  if (sendError) {
150
+ await releaseSendClaim();
105
151
  const errorMessage = sendError.message || "Unknown error";
106
152
  return NextResponse.json({ error: `Failed to send email: ${errorMessage}` }, { status: 502 });
107
153
  }
@@ -158,7 +204,7 @@ const openApi = {
158
204
  { status: 200, description: "Reply sent successfully" },
159
205
  { status: 400, description: "Missing required payload fields" },
160
206
  { status: 404, description: "Reply action not found" },
161
- { status: 409, description: "Action in invalid state for sending" },
207
+ { status: 409, description: "Action in invalid state for sending, or a send is already in progress / completed for this reply" },
162
208
  { status: 502, description: "Email delivery failed" },
163
209
  { status: 503, description: "Email service not configured or disabled" }
164
210
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../../src/modules/inbox_ops/api/proposals/%5Bid%5D/replies/%5BreplyId%5D/send/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { Resend } from 'resend'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { resolveDefaultEmailFromAddress } from '@open-mercato/shared/lib/email/config'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxProposalAction, InboxEmail } from '../../../../../../data/entities'\nimport { draftReplyPayloadSchema } from '../../../../../../data/validators'\nimport { emitInboxOpsEvent } from '../../../../../../events'\nimport { createMessageRecordForReply } from '../../../../../../lib/messagesIntegration'\nimport {\n resolveRequestContext,\n resolveProposal,\n extractPathSegment,\n handleRouteError,\n isErrorResponse,\n} from '../../../../../routeHelpers'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['inbox_ops.replies.send'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveRequestContext(req)\n const url = new URL(req.url)\n const proposal = await resolveProposal(url, ctx)\n if (isErrorResponse(proposal)) return proposal\n\n const replyId = extractPathSegment(url, 'replies')\n if (!replyId) {\n return NextResponse.json({ error: 'Missing reply ID' }, { status: 400 })\n }\n\n const action = await findOneWithDecryption(\n ctx.em,\n InboxProposalAction,\n {\n id: replyId,\n proposalId: proposal.id,\n actionType: 'draft_reply',\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n if (!action) {\n return NextResponse.json({ error: 'Reply action not found' }, { status: 404 })\n }\n\n if (action.status !== 'executed') {\n return NextResponse.json(\n { error: `Cannot send reply \u2014 action must be accepted first (current status: \"${action.status}\")` },\n { status: 409 },\n )\n }\n\n const existingMetadata =\n action.metadata && typeof action.metadata === 'object' ? action.metadata : {}\n if ((existingMetadata as Record<string, unknown>).replySentAt) {\n return NextResponse.json(\n { error: 'Reply has already been sent for this action' },\n { status: 409 },\n )\n }\n\n const email = await findOneWithDecryption(\n ctx.em,\n InboxEmail,\n { id: proposal.inboxEmailId, deletedAt: null },\n undefined,\n ctx.scope,\n )\n\n const payloadResult = draftReplyPayloadSchema.safeParse(action.payload)\n if (!payloadResult.success) {\n return NextResponse.json({ error: 'Reply payload missing required fields (to, subject, body)' }, { status: 400 })\n }\n const { to: toAddress, toName, subject, body } = payloadResult.data\n\n const apiKey = process.env.RESEND_API_KEY\n if (!apiKey) {\n return NextResponse.json({ error: 'Email service not configured' }, { status: 503 })\n }\n\n const emailDisabled =\n parseBooleanWithDefault(process.env.OM_DISABLE_EMAIL_DELIVERY, false) ||\n parseBooleanWithDefault(process.env.OM_TEST_MODE, false)\n if (emailDisabled) {\n return NextResponse.json({ error: 'Email delivery is disabled' }, { status: 503 })\n }\n\n const { inReplyToMessageId, references: payloadReferences } = payloadResult.data\n const fromAddress = resolveDefaultEmailFromAddress()\n if (!fromAddress) {\n return NextResponse.json({\n error: 'Email sender is not configured. Set NOTIFICATIONS_EMAIL_FROM, EMAIL_FROM, or ADMIN_EMAIL.',\n }, { status: 503 })\n }\n\n const headers: Record<string, string> = {}\n const inReplyTo = inReplyToMessageId || email?.messageId\n if (inReplyTo) {\n headers['In-Reply-To'] = inReplyTo\n }\n const references = payloadReferences || (email?.emailReferences as string[])\n if (references && references.length > 0) {\n headers['References'] = references.join(' ')\n }\n\n const resend = new Resend(apiKey)\n const { data: sendData, error: sendError } = await resend.emails.send({\n to: toAddress,\n from: fromAddress,\n subject,\n text: body,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n })\n\n if (sendError) {\n const errorMessage = sendError.message || 'Unknown error'\n return NextResponse.json({ error: `Failed to send email: ${errorMessage}` }, { status: 502 })\n }\n\n const sentMessageId = sendData?.id || null\n const messagesResult = await createMessageRecordForReply(\n { to: toAddress, toName, subject, body },\n proposal.inboxEmailId,\n {\n container: ctx.container,\n scope: {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n },\n },\n )\n\n action.metadata = {\n ...existingMetadata,\n replySentAt: new Date().toISOString(),\n sentMessageId,\n ...(messagesResult ? { messageRecordId: messagesResult.messageId } : {}),\n }\n await ctx.em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.reply.sent', {\n proposalId: proposal.id,\n actionId: replyId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n toAddress,\n sentMessageId,\n messageRecordId: messagesResult?.messageId ?? null,\n })\n } catch (eventError) {\n console.error('[inbox_ops:reply:send] Failed to emit event:', eventError)\n }\n\n return NextResponse.json({\n ok: true,\n sentMessageId,\n ...(messagesResult ? { messageRecordId: messagesResult.messageId } : {}),\n })\n } catch (err) {\n return handleRouteError(err, 'send reply')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Send draft reply',\n methods: {\n POST: {\n summary: 'Send a draft reply email and register it in messages when available',\n description: 'Sends the draft_reply action payload via the configured email provider. When the messages module is available, also records the sent reply as an internal message record. Sets In-Reply-To and References headers for threading.',\n responses: [\n { status: 200, description: 'Reply sent successfully' },\n { status: 400, description: 'Missing required payload fields' },\n { status: 404, description: 'Reply action not found' },\n { status: 409, description: 'Action in invalid state for sending' },\n { status: 502, description: 'Email delivery failed' },\n { status: 503, description: 'Email service not configured or disabled' },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { raw } from '@mikro-orm/core'\nimport { Resend } from 'resend'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { resolveDefaultEmailFromAddress } from '@open-mercato/shared/lib/email/config'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxProposalAction, InboxEmail } from '../../../../../../data/entities'\nimport { draftReplyPayloadSchema } from '../../../../../../data/validators'\nimport { emitInboxOpsEvent } from '../../../../../../events'\nimport { createMessageRecordForReply } from '../../../../../../lib/messagesIntegration'\nimport {\n resolveRequestContext,\n resolveProposal,\n extractPathSegment,\n handleRouteError,\n isErrorResponse,\n} from '../../../../../routeHelpers'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['inbox_ops.replies.send'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveRequestContext(req)\n const url = new URL(req.url)\n const proposal = await resolveProposal(url, ctx)\n if (isErrorResponse(proposal)) return proposal\n\n const replyId = extractPathSegment(url, 'replies')\n if (!replyId) {\n return NextResponse.json({ error: 'Missing reply ID' }, { status: 400 })\n }\n\n const action = await findOneWithDecryption(\n ctx.em,\n InboxProposalAction,\n {\n id: replyId,\n proposalId: proposal.id,\n actionType: 'draft_reply',\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n if (!action) {\n return NextResponse.json({ error: 'Reply action not found' }, { status: 404 })\n }\n\n if (action.status !== 'executed') {\n return NextResponse.json(\n { error: `Cannot send reply \u2014 action must be accepted first (current status: \"${action.status}\")` },\n { status: 409 },\n )\n }\n\n const existingMetadata =\n action.metadata && typeof action.metadata === 'object' ? action.metadata : {}\n if ((existingMetadata as Record<string, unknown>).replySentAt) {\n return NextResponse.json(\n { error: 'Reply has already been sent for this action' },\n { status: 409 },\n )\n }\n\n const email = await findOneWithDecryption(\n ctx.em,\n InboxEmail,\n { id: proposal.inboxEmailId, deletedAt: null },\n undefined,\n ctx.scope,\n )\n\n const payloadResult = draftReplyPayloadSchema.safeParse(action.payload)\n if (!payloadResult.success) {\n return NextResponse.json({ error: 'Reply payload missing required fields (to, subject, body)' }, { status: 400 })\n }\n const { to: toAddress, toName, subject, body } = payloadResult.data\n\n const apiKey = process.env.RESEND_API_KEY\n if (!apiKey) {\n return NextResponse.json({ error: 'Email service not configured' }, { status: 503 })\n }\n\n const emailDisabled =\n parseBooleanWithDefault(process.env.OM_DISABLE_EMAIL_DELIVERY, false) ||\n parseBooleanWithDefault(process.env.OM_TEST_MODE, false)\n if (emailDisabled) {\n return NextResponse.json({ error: 'Email delivery is disabled' }, { status: 503 })\n }\n\n const { inReplyToMessageId, references: payloadReferences } = payloadResult.data\n const fromAddress = resolveDefaultEmailFromAddress()\n if (!fromAddress) {\n return NextResponse.json({\n error: 'Email sender is not configured. Set NOTIFICATIONS_EMAIL_FROM, EMAIL_FROM, or ADMIN_EMAIL.',\n }, { status: 503 })\n }\n\n const headers: Record<string, string> = {}\n const inReplyTo = inReplyToMessageId || email?.messageId\n if (inReplyTo) {\n headers['In-Reply-To'] = inReplyTo\n }\n const references = payloadReferences || (email?.emailReferences as string[])\n if (references && references.length > 0) {\n headers['References'] = references.join(' ')\n }\n\n // Atomically claim the send right before calling the external email provider so\n // two concurrent requests cannot both deliver the same reply. The claim only\n // succeeds when neither replySentAt nor replySendClaimedAt is already set in the\n // metadata; a losing request gets 0 rows back and is rejected with 409.\n const sendClaimedAt = new Date().toISOString()\n const claimed = await ctx.em.nativeUpdate(\n InboxProposalAction,\n {\n id: action.id,\n proposalId: proposal.id,\n actionType: 'draft_reply',\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n status: 'executed',\n [raw(`coalesce(\"metadata\"->>'replySentAt', '')`)]: '',\n [raw(`coalesce(\"metadata\"->>'replySendClaimedAt', '')`)]: '',\n },\n {\n metadata: raw(\n `coalesce(\"metadata\", '{}'::jsonb) || jsonb_build_object('replySendClaimedAt', ?::text)`,\n [sendClaimedAt],\n ),\n },\n )\n\n if (claimed === 0) {\n return NextResponse.json(\n { error: 'Reply send is already in progress or has been sent for this action' },\n { status: 409 },\n )\n }\n\n const releaseSendClaim = async () => {\n try {\n await ctx.em.nativeUpdate(\n InboxProposalAction,\n {\n id: action.id,\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n [raw(`coalesce(\"metadata\"->>'replySentAt', '')`)]: '',\n [raw(`(\"metadata\"->>'replySendClaimedAt')`)]: sendClaimedAt,\n },\n { metadata: raw(`\"metadata\" - 'replySendClaimedAt'`) },\n )\n } catch (releaseError) {\n console.error('[inbox_ops:reply:send] Failed to release send claim:', releaseError)\n }\n }\n\n const resend = new Resend(apiKey)\n const { data: sendData, error: sendError } = await resend.emails.send({\n to: toAddress,\n from: fromAddress,\n subject,\n text: body,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n })\n\n if (sendError) {\n await releaseSendClaim()\n const errorMessage = sendError.message || 'Unknown error'\n return NextResponse.json({ error: `Failed to send email: ${errorMessage}` }, { status: 502 })\n }\n\n const sentMessageId = sendData?.id || null\n const messagesResult = await createMessageRecordForReply(\n { to: toAddress, toName, subject, body },\n proposal.inboxEmailId,\n {\n container: ctx.container,\n scope: {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n },\n },\n )\n\n action.metadata = {\n ...existingMetadata,\n replySentAt: new Date().toISOString(),\n sentMessageId,\n ...(messagesResult ? { messageRecordId: messagesResult.messageId } : {}),\n }\n await ctx.em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.reply.sent', {\n proposalId: proposal.id,\n actionId: replyId,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n toAddress,\n sentMessageId,\n messageRecordId: messagesResult?.messageId ?? null,\n })\n } catch (eventError) {\n console.error('[inbox_ops:reply:send] Failed to emit event:', eventError)\n }\n\n return NextResponse.json({\n ok: true,\n sentMessageId,\n ...(messagesResult ? { messageRecordId: messagesResult.messageId } : {}),\n })\n } catch (err) {\n return handleRouteError(err, 'send reply')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Send draft reply',\n methods: {\n POST: {\n summary: 'Send a draft reply email and register it in messages when available',\n description: 'Sends the draft_reply action payload via the configured email provider. When the messages module is available, also records the sent reply as an internal message record. Sets In-Reply-To and References headers for threading.',\n responses: [\n { status: 200, description: 'Reply sent successfully' },\n { status: 400, description: 'Missing required payload fields' },\n { status: 404, description: 'Reply action not found' },\n { status: 409, description: 'Action in invalid state for sending, or a send is already in progress / completed for this reply' },\n { status: 502, description: 'Email delivery failed' },\n { status: 503, description: 'Email service not configured or disabled' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,35 @@
1
+ import {
2
+ readVersionedPreference,
3
+ writeVersionedPreference,
4
+ clearVersionedPreference
5
+ } from "@open-mercato/shared/lib/browser/versionedPreference";
6
+ const LEGACY_HIDDEN_WIDGETS_KEY = "om:portal:dashboard:hidden";
7
+ const HIDDEN_WIDGETS_KEY_PREFIX = "om:portal:dashboard:hidden:v1:";
8
+ const HIDDEN_WIDGETS_VERSION = 1;
9
+ function buildHiddenWidgetsKey(orgSlug, userId) {
10
+ return `${HIDDEN_WIDGETS_KEY_PREFIX}${orgSlug}:${userId}`;
11
+ }
12
+ function isStringArray(value) {
13
+ return Array.isArray(value) && value.every((id) => typeof id === "string");
14
+ }
15
+ function loadHiddenWidgets(orgSlug, userId) {
16
+ const hidden = readVersionedPreference(
17
+ buildHiddenWidgetsKey(orgSlug, userId),
18
+ HIDDEN_WIDGETS_VERSION,
19
+ isStringArray,
20
+ []
21
+ );
22
+ return new Set(hidden);
23
+ }
24
+ function saveHiddenWidgets(orgSlug, userId, hidden) {
25
+ writeVersionedPreference(buildHiddenWidgetsKey(orgSlug, userId), HIDDEN_WIDGETS_VERSION, Array.from(hidden));
26
+ }
27
+ function clearLegacyHiddenWidgetsKey() {
28
+ clearVersionedPreference(LEGACY_HIDDEN_WIDGETS_KEY);
29
+ }
30
+ export {
31
+ clearLegacyHiddenWidgetsKey,
32
+ loadHiddenWidgets,
33
+ saveHiddenWidgets
34
+ };
35
+ //# sourceMappingURL=hiddenWidgetsStorage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/dashboard/hiddenWidgetsStorage.ts"],
4
+ "sourcesContent": ["import {\n readVersionedPreference,\n writeVersionedPreference,\n clearVersionedPreference,\n} from '@open-mercato/shared/lib/browser/versionedPreference'\n\nconst LEGACY_HIDDEN_WIDGETS_KEY = 'om:portal:dashboard:hidden'\nconst HIDDEN_WIDGETS_KEY_PREFIX = 'om:portal:dashboard:hidden:v1:'\nconst HIDDEN_WIDGETS_VERSION = 1\n\nfunction buildHiddenWidgetsKey(orgSlug: string, userId: string): string {\n return `${HIDDEN_WIDGETS_KEY_PREFIX}${orgSlug}:${userId}`\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((id) => typeof id === 'string')\n}\n\nexport function loadHiddenWidgets(orgSlug: string, userId: string): Set<string> {\n const hidden = readVersionedPreference<string[]>(\n buildHiddenWidgetsKey(orgSlug, userId),\n HIDDEN_WIDGETS_VERSION,\n isStringArray,\n [],\n )\n return new Set(hidden)\n}\n\nexport function saveHiddenWidgets(orgSlug: string, userId: string, hidden: Set<string>): void {\n writeVersionedPreference(buildHiddenWidgetsKey(orgSlug, userId), HIDDEN_WIDGETS_VERSION, Array.from(hidden))\n}\n\n/** Drops the pre-scoping global key so stale preferences stop leaking across org/user contexts. */\nexport function clearLegacyHiddenWidgetsKey(): void {\n clearVersionedPreference(LEGACY_HIDDEN_WIDGETS_KEY)\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,4BAA4B;AAClC,MAAM,4BAA4B;AAClC,MAAM,yBAAyB;AAE/B,SAAS,sBAAsB,SAAiB,QAAwB;AACtE,SAAO,GAAG,yBAAyB,GAAG,OAAO,IAAI,MAAM;AACzD;AAEA,SAAS,cAAc,OAAmC;AACxD,SAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,OAAO,OAAO,OAAO,QAAQ;AAC3E;AAEO,SAAS,kBAAkB,SAAiB,QAA6B;AAC9E,QAAM,SAAS;AAAA,IACb,sBAAsB,SAAS,MAAM;AAAA,IACrC;AAAA,IACA;AAAA,IACA,CAAC;AAAA,EACH;AACA,SAAO,IAAI,IAAI,MAAM;AACvB;AAEO,SAAS,kBAAkB,SAAiB,QAAgB,QAA2B;AAC5F,2BAAyB,sBAAsB,SAAS,MAAM,GAAG,wBAAwB,MAAM,KAAK,MAAM,CAAC;AAC7G;AAGO,SAAS,8BAAoC;AAClD,2BAAyB,yBAAyB;AACpD;",
6
+ "names": []
7
+ }
@@ -12,22 +12,11 @@ import { PortalEmptyState } from "@open-mercato/ui/portal/components/PortalEmpty
12
12
  import { usePortalDashboardWidgets } from "@open-mercato/ui/portal/hooks/usePortalDashboardWidgets";
13
13
  import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
14
14
  import { PortalInjectionSpots } from "@open-mercato/ui/backend/injection/spotIds";
15
- const HIDDEN_WIDGETS_KEY = "om:portal:dashboard:hidden";
16
- function loadHiddenWidgets() {
17
- try {
18
- const raw = localStorage.getItem(HIDDEN_WIDGETS_KEY);
19
- if (!raw) return /* @__PURE__ */ new Set();
20
- return new Set(JSON.parse(raw));
21
- } catch {
22
- return /* @__PURE__ */ new Set();
23
- }
24
- }
25
- function saveHiddenWidgets(hidden) {
26
- try {
27
- localStorage.setItem(HIDDEN_WIDGETS_KEY, JSON.stringify(Array.from(hidden)));
28
- } catch {
29
- }
30
- }
15
+ import {
16
+ loadHiddenWidgets,
17
+ saveHiddenWidgets,
18
+ clearLegacyHiddenWidgetsKey
19
+ } from "./hiddenWidgetsStorage.js";
31
20
  function WidgetIcon({ className }) {
32
21
  return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
33
22
  /* @__PURE__ */ jsx("rect", { width: "7", height: "9", x: "3", y: "3", rx: "1" }),
@@ -42,14 +31,24 @@ function PortalDashboardPage({ params }) {
42
31
  const { auth } = usePortalContext();
43
32
  const { user, loading } = auth;
44
33
  const [editing, setEditing] = useState(false);
45
- const [hiddenWidgets, setHiddenWidgets] = useState(() => loadHiddenWidgets());
34
+ const [hiddenWidgetsScopeKey, setHiddenWidgetsScopeKey] = useState(null);
35
+ const [hiddenWidgets, setHiddenWidgets] = useState(() => /* @__PURE__ */ new Set());
46
36
  const { widgets: dashboardWidgets, isLoading: widgetsLoading } = usePortalDashboardWidgets("portal:dashboard:sections");
37
+ const activeHiddenWidgetsScopeKey = user ? `${params.orgSlug}:${user.id}` : null;
38
+ if (activeHiddenWidgetsScopeKey !== hiddenWidgetsScopeKey) {
39
+ setHiddenWidgetsScopeKey(activeHiddenWidgetsScopeKey);
40
+ setHiddenWidgets(user ? loadHiddenWidgets(params.orgSlug, user.id) : /* @__PURE__ */ new Set());
41
+ }
47
42
  useEffect(() => {
48
43
  if (!loading && !user) {
49
44
  router.replace(`/${params.orgSlug}/portal/login`);
50
45
  }
51
46
  }, [loading, user, router, params.orgSlug]);
47
+ useEffect(() => {
48
+ clearLegacyHiddenWidgetsKey();
49
+ }, []);
52
50
  const toggleWidget = useCallback((widgetId) => {
51
+ if (!user) return;
53
52
  setHiddenWidgets((prev) => {
54
53
  const next = new Set(prev);
55
54
  if (next.has(widgetId)) {
@@ -57,10 +56,10 @@ function PortalDashboardPage({ params }) {
57
56
  } else {
58
57
  next.add(widgetId);
59
58
  }
60
- saveHiddenWidgets(next);
59
+ saveHiddenWidgets(params.orgSlug, user.id, next);
61
60
  return next;
62
61
  });
63
- }, []);
62
+ }, [params.orgSlug, user?.id]);
64
63
  const visibleWidgets = useMemo(
65
64
  () => dashboardWidgets.filter((w) => !hiddenWidgets.has(w.metadata.id)),
66
65
  [dashboardWidgets, hiddenWidgets]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/dashboard/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport React, { useEffect, useMemo, useState, useCallback } from 'react'\nimport { useRouter } from 'next/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 { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'\nimport { PortalCard, PortalCardHeader } from '@open-mercato/ui/portal/components/PortalCard'\nimport { PortalEmptyState } from '@open-mercato/ui/portal/components/PortalEmptyState'\nimport { usePortalDashboardWidgets } from '@open-mercato/ui/portal/hooks/usePortalDashboardWidgets'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { PortalInjectionSpots } from '@open-mercato/ui/backend/injection/spotIds'\n\ntype Props = { params: { orgSlug: string } }\n\nconst HIDDEN_WIDGETS_KEY = 'om:portal:dashboard:hidden'\n\nfunction loadHiddenWidgets(): Set<string> {\n try {\n const raw = localStorage.getItem(HIDDEN_WIDGETS_KEY)\n if (!raw) return new Set()\n return new Set(JSON.parse(raw))\n } catch {\n return new Set()\n }\n}\n\nfunction saveHiddenWidgets(hidden: Set<string>) {\n try {\n localStorage.setItem(HIDDEN_WIDGETS_KEY, JSON.stringify(Array.from(hidden)))\n } catch {\n // best effort\n }\n}\n\nfunction WidgetIcon({ className }: { className?: string }) {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\n <rect width=\"7\" height=\"9\" x=\"3\" y=\"3\" rx=\"1\" /><rect width=\"7\" height=\"5\" x=\"14\" y=\"3\" rx=\"1\" /><rect width=\"7\" height=\"5\" x=\"14\" y=\"12\" rx=\"1\" /><rect width=\"7\" height=\"9\" x=\"3\" y=\"16\" rx=\"1\" />\n </svg>\n )\n}\n\nexport default function PortalDashboardPage({ params }: Props) {\n const t = useT()\n const router = useRouter()\n const { auth } = usePortalContext()\n const { user, loading } = auth\n\n const [editing, setEditing] = useState(false)\n const [hiddenWidgets, setHiddenWidgets] = useState<Set<string>>(() => loadHiddenWidgets())\n\n const { widgets: dashboardWidgets, isLoading: widgetsLoading } = usePortalDashboardWidgets('portal:dashboard:sections' as any)\n\n useEffect(() => {\n if (!loading && !user) {\n router.replace(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, router, params.orgSlug])\n\n const toggleWidget = useCallback((widgetId: string) => {\n setHiddenWidgets((prev) => {\n const next = new Set(prev)\n if (next.has(widgetId)) {\n next.delete(widgetId)\n } else {\n next.add(widgetId)\n }\n saveHiddenWidgets(next)\n return next\n })\n }, [])\n\n const visibleWidgets = useMemo(\n () => dashboardWidgets.filter((w) => !hiddenWidgets.has(w.metadata.id)),\n [dashboardWidgets, hiddenWidgets],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug: params.orgSlug, user, roles: auth.roles, resolvedFeatures: auth.resolvedFeatures }),\n [params.orgSlug, user, auth.roles, auth.resolvedFeatures],\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 return (\n <div className=\"flex flex-col gap-8\">\n <PortalPageHeader\n label={t('portal.dashboard.title', 'Dashboard')}\n title={t('portal.dashboard.welcome', { name: user.displayName })}\n action={\n dashboardWidgets.length > 0 ? (\n <Button\n type=\"button\"\n variant={editing ? 'default' : 'outline'}\n size=\"sm\"\n className=\"rounded-lg text-sm\"\n onClick={() => setEditing((prev) => !prev)}\n >\n {editing ? t('portal.dashboard.done', 'Done') : t('portal.dashboard.customize', 'Customize')}\n </Button>\n ) : null\n }\n />\n\n <InjectionSpot spotId={PortalInjectionSpots.pageBefore('dashboard')} context={injectionContext} />\n\n {editing && dashboardWidgets.length > 0 ? (\n <PortalCard>\n <PortalCardHeader\n label={t('portal.dashboard.customize', 'Customize')}\n title={t('portal.dashboard.widgets', 'Dashboard Widgets')}\n />\n <div className=\"flex flex-wrap gap-2\">\n {dashboardWidgets.map((widget) => {\n const isHidden = hiddenWidgets.has(widget.metadata.id)\n return (\n <Button key={widget.metadata.id} type=\"button\" variant={isHidden ? 'outline' : 'default'} size=\"sm\" className=\"rounded-lg text-sm\" onClick={() => toggleWidget(widget.metadata.id)}>\n {widget.metadata.title || widget.metadata.id}\n </Button>\n )\n })}\n </div>\n </PortalCard>\n ) : null}\n\n {visibleWidgets.length > 0 ? (\n <div className=\"grid gap-5 md:grid-cols-2 xl:grid-cols-3\">\n {visibleWidgets.map((widget) => {\n const WidgetComponent = widget.Widget\n if (!WidgetComponent) return null\n return (\n <PortalCard key={widget.metadata.id}>\n <PortalCardHeader title={widget.metadata.title || widget.metadata.id} />\n <WidgetComponent context={{ orgSlug: params.orgSlug, user, roles: auth.roles, resolvedFeatures: auth.resolvedFeatures }} />\n </PortalCard>\n )\n })}\n </div>\n ) : null}\n\n {dashboardWidgets.length === 0 && !widgetsLoading ? (\n <PortalEmptyState\n icon={<WidgetIcon className=\"size-5\" />}\n title={t('portal.dashboard.emptyWidgets', 'No dashboard widgets yet')}\n description=\"Modules can inject widgets into this dashboard via the portal:dashboard:sections injection spot.\"\n />\n ) : null}\n\n <InjectionSpot spotId={PortalInjectionSpots.pageAfter('dashboard')} context={injectionContext} />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport React, { useEffect, useMemo, useState, useCallback } from 'react'\nimport { useRouter } from 'next/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 { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'\nimport { PortalCard, PortalCardHeader } from '@open-mercato/ui/portal/components/PortalCard'\nimport { PortalEmptyState } from '@open-mercato/ui/portal/components/PortalEmptyState'\nimport { usePortalDashboardWidgets } from '@open-mercato/ui/portal/hooks/usePortalDashboardWidgets'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { PortalInjectionSpots } from '@open-mercato/ui/backend/injection/spotIds'\nimport {\n loadHiddenWidgets,\n saveHiddenWidgets,\n clearLegacyHiddenWidgetsKey,\n} from './hiddenWidgetsStorage'\n\ntype Props = { params: { orgSlug: string } }\n\nfunction WidgetIcon({ className }: { className?: string }) {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\n <rect width=\"7\" height=\"9\" x=\"3\" y=\"3\" rx=\"1\" /><rect width=\"7\" height=\"5\" x=\"14\" y=\"3\" rx=\"1\" /><rect width=\"7\" height=\"5\" x=\"14\" y=\"12\" rx=\"1\" /><rect width=\"7\" height=\"9\" x=\"3\" y=\"16\" rx=\"1\" />\n </svg>\n )\n}\n\nexport default function PortalDashboardPage({ params }: Props) {\n const t = useT()\n const router = useRouter()\n const { auth } = usePortalContext()\n const { user, loading } = auth\n\n const [editing, setEditing] = useState(false)\n const [hiddenWidgetsScopeKey, setHiddenWidgetsScopeKey] = useState<string | null>(null)\n const [hiddenWidgets, setHiddenWidgets] = useState<Set<string>>(() => new Set())\n\n const { widgets: dashboardWidgets, isLoading: widgetsLoading } = usePortalDashboardWidgets('portal:dashboard:sections' as any)\n\n // Recompute synchronously during render (not in an effect) so a freshly\n // resolved `user` never paints a frame with the previous scope's hidden set.\n const activeHiddenWidgetsScopeKey = user ? `${params.orgSlug}:${user.id}` : null\n if (activeHiddenWidgetsScopeKey !== hiddenWidgetsScopeKey) {\n setHiddenWidgetsScopeKey(activeHiddenWidgetsScopeKey)\n setHiddenWidgets(user ? loadHiddenWidgets(params.orgSlug, user.id) : new Set())\n }\n\n useEffect(() => {\n if (!loading && !user) {\n router.replace(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, router, params.orgSlug])\n\n useEffect(() => {\n clearLegacyHiddenWidgetsKey()\n }, [])\n\n const toggleWidget = useCallback((widgetId: string) => {\n if (!user) return\n setHiddenWidgets((prev) => {\n const next = new Set(prev)\n if (next.has(widgetId)) {\n next.delete(widgetId)\n } else {\n next.add(widgetId)\n }\n saveHiddenWidgets(params.orgSlug, user.id, next)\n return next\n })\n }, [params.orgSlug, user?.id])\n\n const visibleWidgets = useMemo(\n () => dashboardWidgets.filter((w) => !hiddenWidgets.has(w.metadata.id)),\n [dashboardWidgets, hiddenWidgets],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug: params.orgSlug, user, roles: auth.roles, resolvedFeatures: auth.resolvedFeatures }),\n [params.orgSlug, user, auth.roles, auth.resolvedFeatures],\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 return (\n <div className=\"flex flex-col gap-8\">\n <PortalPageHeader\n label={t('portal.dashboard.title', 'Dashboard')}\n title={t('portal.dashboard.welcome', { name: user.displayName })}\n action={\n dashboardWidgets.length > 0 ? (\n <Button\n type=\"button\"\n variant={editing ? 'default' : 'outline'}\n size=\"sm\"\n className=\"rounded-lg text-sm\"\n onClick={() => setEditing((prev) => !prev)}\n >\n {editing ? t('portal.dashboard.done', 'Done') : t('portal.dashboard.customize', 'Customize')}\n </Button>\n ) : null\n }\n />\n\n <InjectionSpot spotId={PortalInjectionSpots.pageBefore('dashboard')} context={injectionContext} />\n\n {editing && dashboardWidgets.length > 0 ? (\n <PortalCard>\n <PortalCardHeader\n label={t('portal.dashboard.customize', 'Customize')}\n title={t('portal.dashboard.widgets', 'Dashboard Widgets')}\n />\n <div className=\"flex flex-wrap gap-2\">\n {dashboardWidgets.map((widget) => {\n const isHidden = hiddenWidgets.has(widget.metadata.id)\n return (\n <Button key={widget.metadata.id} type=\"button\" variant={isHidden ? 'outline' : 'default'} size=\"sm\" className=\"rounded-lg text-sm\" onClick={() => toggleWidget(widget.metadata.id)}>\n {widget.metadata.title || widget.metadata.id}\n </Button>\n )\n })}\n </div>\n </PortalCard>\n ) : null}\n\n {visibleWidgets.length > 0 ? (\n <div className=\"grid gap-5 md:grid-cols-2 xl:grid-cols-3\">\n {visibleWidgets.map((widget) => {\n const WidgetComponent = widget.Widget\n if (!WidgetComponent) return null\n return (\n <PortalCard key={widget.metadata.id}>\n <PortalCardHeader title={widget.metadata.title || widget.metadata.id} />\n <WidgetComponent context={{ orgSlug: params.orgSlug, user, roles: auth.roles, resolvedFeatures: auth.resolvedFeatures }} />\n </PortalCard>\n )\n })}\n </div>\n ) : null}\n\n {dashboardWidgets.length === 0 && !widgetsLoading ? (\n <PortalEmptyState\n icon={<WidgetIcon className=\"size-5\" />}\n title={t('portal.dashboard.emptyWidgets', 'No dashboard widgets yet')}\n description=\"Modules can inject widgets into this dashboard via the portal:dashboard:sections injection spot.\"\n />\n ) : null}\n\n <InjectionSpot spotId={PortalInjectionSpots.pageAfter('dashboard')} context={injectionContext} />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6286.1.d985c5c374",
3
+ "version": "0.6.6-develop.6296.1.5e8bdfee17",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -246,16 +246,16 @@
246
246
  "zod": "^4.4.3"
247
247
  },
248
248
  "peerDependencies": {
249
- "@open-mercato/ai-assistant": "0.6.6-develop.6286.1.d985c5c374",
250
- "@open-mercato/shared": "0.6.6-develop.6286.1.d985c5c374",
251
- "@open-mercato/ui": "0.6.6-develop.6286.1.d985c5c374",
249
+ "@open-mercato/ai-assistant": "0.6.6-develop.6296.1.5e8bdfee17",
250
+ "@open-mercato/shared": "0.6.6-develop.6296.1.5e8bdfee17",
251
+ "@open-mercato/ui": "0.6.6-develop.6296.1.5e8bdfee17",
252
252
  "react": "^19.0.0",
253
253
  "react-dom": "^19.0.0"
254
254
  },
255
255
  "devDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6286.1.d985c5c374",
257
- "@open-mercato/shared": "0.6.6-develop.6286.1.d985c5c374",
258
- "@open-mercato/ui": "0.6.6-develop.6286.1.d985c5c374",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6296.1.5e8bdfee17",
257
+ "@open-mercato/shared": "0.6.6-develop.6296.1.5e8bdfee17",
258
+ "@open-mercato/ui": "0.6.6-develop.6296.1.5e8bdfee17",
259
259
  "@testing-library/dom": "^10.4.1",
260
260
  "@testing-library/jest-dom": "^6.9.1",
261
261
  "@testing-library/react": "^16.3.1",