@open-mercato/core 0.10.1-develop.7761.1.9dfaf470ed → 0.10.1-develop.7763.1.caea616ebf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/profile/route.js +16 -2
- package/dist/modules/auth/api/profile/route.js.map +2 -2
- package/dist/modules/auth/lib/userDataSubject.js +26 -0
- package/dist/modules/auth/lib/userDataSubject.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +1 -0
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +3 -2
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +11 -12
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/route.js +3 -2
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/create/page.js +6 -0
- package/dist/modules/customers/backend/customers/companies/create/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +25 -25
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.js +8 -27
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +175 -171
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js +25 -24
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/create/page.js +9 -0
- package/dist/modules/customers/backend/customers/people/create/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +140 -144
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js +2 -0
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +13 -4
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +13 -3
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/CustomerDetailSidebar.js +29 -0
- package/dist/modules/customers/components/detail/CustomerDetailSidebar.js.map +7 -0
- package/dist/modules/customers/components/detail/DealDetailHeader.js +4 -2
- package/dist/modules/customers/components/detail/DealDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/DealDetailTabs.js +9 -1
- package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +5 -0
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailHeader.js +2 -0
- package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +9 -1
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/create/CreateDealForm.js +160 -176
- package/dist/modules/customers/components/detail/create/CreateDealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/useCustomerInjectedTabs.js +47 -0
- package/dist/modules/customers/components/detail/useCustomerInjectedTabs.js.map +7 -0
- package/dist/modules/customers/components/formConfig.js +3 -0
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/extension-points.js +14 -4
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/detailResponseEnrichment.js +38 -0
- package/dist/modules/customers/lib/detailResponseEnrichment.js.map +7 -0
- package/dist/modules/dashboards/api/layout/[itemId]/route.js +11 -4
- package/dist/modules/dashboards/api/layout/[itemId]/route.js.map +2 -2
- package/dist/modules/dashboards/api/layout/route.js +25 -13
- package/dist/modules/dashboards/api/layout/route.js.map +2 -2
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js +4 -4
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/profile/route.ts +15 -1
- package/src/modules/auth/lib/userDataSubject.ts +30 -0
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -0
- package/src/modules/customers/api/companies/[id]/route.ts +3 -2
- package/src/modules/customers/api/deals/[id]/route.ts +12 -12
- package/src/modules/customers/api/people/[id]/route.ts +3 -2
- package/src/modules/customers/backend/customers/companies/create/page.tsx +6 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +27 -30
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealInjectedTabs.tsx +9 -44
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -3
- package/src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx +25 -27
- package/src/modules/customers/backend/customers/people/create/page.tsx +9 -0
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +21 -33
- package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +3 -0
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +15 -4
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +15 -4
- package/src/modules/customers/components/detail/CustomerDetailSidebar.tsx +41 -0
- package/src/modules/customers/components/detail/DealDetailHeader.tsx +4 -1
- package/src/modules/customers/components/detail/DealDetailTabs.tsx +11 -1
- package/src/modules/customers/components/detail/DealForm.tsx +5 -0
- package/src/modules/customers/components/detail/PersonDetailHeader.tsx +3 -0
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +11 -1
- package/src/modules/customers/components/detail/create/CreateDealForm.tsx +126 -209
- package/src/modules/customers/components/detail/useCustomerInjectedTabs.tsx +64 -0
- package/src/modules/customers/components/formConfig.tsx +3 -0
- package/src/modules/customers/extension-points.ts +14 -4
- package/src/modules/customers/lib/detailResponseEnrichment.ts +53 -0
- package/src/modules/dashboards/api/layout/[itemId]/route.ts +12 -4
- package/src/modules/dashboards/api/layout/route.ts +25 -12
- package/src/modules/integrations/widgets/injection/external-ids/widget.client.tsx +4 -4
|
@@ -5,11 +5,12 @@ import {
|
|
|
5
5
|
defineModuleExtensionPoints,
|
|
6
6
|
injectionExtensionHost
|
|
7
7
|
} from "@open-mercato/shared/modules/widgets/extension-points";
|
|
8
|
-
const detailHost = (spotId, source) => injectionExtensionHost({
|
|
8
|
+
const detailHost = (spotId, source, dataContract) => injectionExtensionHost({
|
|
9
9
|
family: "detail",
|
|
10
10
|
spotId,
|
|
11
11
|
supported: ["render-widget"],
|
|
12
12
|
contextContract: "customers.detail.v1",
|
|
13
|
+
dataContract,
|
|
13
14
|
source
|
|
14
15
|
});
|
|
15
16
|
const extensionPoints = defineModuleExtensionPoints({
|
|
@@ -19,9 +20,9 @@ const extensionPoints = defineModuleExtensionPoints({
|
|
|
19
20
|
dealsTable: dataTableExtensionHost({ tableId: "customers.deals.list", source: "backend/customers/deals/page.tsx" }),
|
|
20
21
|
peopleTable: dataTableExtensionHost({ tableId: "customers.people.list", source: "backend/customers/people/page.tsx" }),
|
|
21
22
|
todosTable: dataTableExtensionHost({ tableId: "customers.todos.list", source: "components/CustomerTodosTable.tsx" }),
|
|
22
|
-
companyForm: crudFormExtensionHost({ entityId: "customers.company", spotId: "crud-form:customers.company", source: "backend/customers/companies-v2/[id]/page.tsx" }),
|
|
23
|
-
dealForm: crudFormExtensionHost({ entityId: "customers.deal", spotId: "crud-form:customers.deal", source: "backend/customers/deals/[id]/page.tsx" }),
|
|
24
|
-
personForm: crudFormExtensionHost({ entityId: "customers.person", spotId: "crud-form:customers.person", source: "backend/customers/people-v2/[id]/page.tsx" }),
|
|
23
|
+
companyForm: crudFormExtensionHost({ entityId: "customers.company", spotId: "crud-form:customers.company", source: ["backend/customers/companies-v2/[id]/page.tsx", "backend/customers/companies/create/page.tsx"] }),
|
|
24
|
+
dealForm: crudFormExtensionHost({ entityId: "customers.deal", spotId: "crud-form:customers.deal", source: ["backend/customers/deals/[id]/page.tsx", "components/detail/create/CreateDealForm.tsx", "backend/customers/deals/pipeline/components/QuickDealDialog.tsx"] }),
|
|
25
|
+
personForm: crudFormExtensionHost({ entityId: "customers.person", spotId: "crud-form:customers.person", source: ["backend/customers/people-v2/[id]/page.tsx", "backend/customers/people/create/page.tsx", "components/detail/CreatePersonDialog.tsx"] }),
|
|
25
26
|
calendarEventTypes: injectionExtensionHost({
|
|
26
27
|
family: "generic",
|
|
27
28
|
spotId: "calendar:customers.event-types",
|
|
@@ -34,13 +35,22 @@ const extensionPoints = defineModuleExtensionPoints({
|
|
|
34
35
|
propsContract: "customers.calendar.event-type-panel.v1",
|
|
35
36
|
source: "components/calendar/editor/EventTypePanel.tsx"
|
|
36
37
|
}),
|
|
38
|
+
companyHeaderActions: detailHost("detail:customers.company:header-actions", "backend/customers/companies-v2/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
39
|
+
companySidebar: detailHost("detail:customers.company:sidebar", "backend/customers/companies-v2/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
40
|
+
companyTabs: detailHost("detail:customers.company:tabs", "backend/customers/companies-v2/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
37
41
|
companyHeader: detailHost("detail:customers.company:header", "backend/customers/companies-v2/[id]/page.tsx"),
|
|
38
42
|
companyStatusBadges: detailHost("detail:customers.company:status-badges", "backend/customers/companies-v2/[id]/page.tsx"),
|
|
39
43
|
companyFooter: detailHost("detail:customers.company:footer", "backend/customers/companies-v2/[id]/page.tsx"),
|
|
40
44
|
companyLegacyDetails: detailHost("customers.company.detail:details", "backend/customers/companies/[id]/page.tsx"),
|
|
45
|
+
dealHeaderActions: detailHost("detail:customers.deal:header-actions", "backend/customers/deals/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
46
|
+
dealSidebar: detailHost("detail:customers.deal:sidebar", "backend/customers/deals/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
47
|
+
dealTabs: detailHost("detail:customers.deal:tabs", "backend/customers/deals/[id]/hooks/useDealInjectedTabs.tsx", "customers.detail.overview.v1"),
|
|
41
48
|
dealHeader: detailHost("detail:customers.deal:header", "backend/customers/deals/[id]/page.tsx"),
|
|
42
49
|
dealStatusBadges: detailHost("detail:customers.deal:status-badges", "backend/customers/deals/[id]/page.tsx"),
|
|
43
50
|
dealFooter: detailHost("detail:customers.deal:footer", "backend/customers/deals/[id]/page.tsx"),
|
|
51
|
+
personHeaderActions: detailHost("detail:customers.person:header-actions", "backend/customers/people-v2/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
52
|
+
personSidebar: detailHost("detail:customers.person:sidebar", "backend/customers/people-v2/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
53
|
+
personTabs: detailHost("detail:customers.person:tabs", "backend/customers/people-v2/[id]/page.tsx", "customers.detail.overview.v1"),
|
|
44
54
|
personHeader: detailHost("detail:customers.person:header", "backend/customers/people-v2/[id]/page.tsx"),
|
|
45
55
|
personStatusBadges: detailHost("detail:customers.person:status-badges", "backend/customers/people-v2/[id]/page.tsx"),
|
|
46
56
|
personFooter: detailHost("detail:customers.person:footer", "backend/customers/people-v2/[id]/page.tsx"),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/customers/extension-points.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n componentExtensionHost,\n crudFormExtensionHost,\n dataTableExtensionHost,\n defineModuleExtensionPoints,\n injectionExtensionHost,\n} from '@open-mercato/shared/modules/widgets/extension-points'\n\nconst detailHost = (spotId: string, source: string) => injectionExtensionHost({\n family: 'detail',\n spotId,\n supported: ['render-widget'],\n contextContract: 'customers.detail.v1',\n source,\n})\n\nexport const extensionPoints = defineModuleExtensionPoints({\n moduleId: 'customers',\n hosts: {\n companiesTable: dataTableExtensionHost({ tableId: 'customers.companies.list', source: 'backend/customers/companies/page.tsx' }),\n dealsTable: dataTableExtensionHost({ tableId: 'customers.deals.list', source: 'backend/customers/deals/page.tsx' }),\n peopleTable: dataTableExtensionHost({ tableId: 'customers.people.list', source: 'backend/customers/people/page.tsx' }),\n todosTable: dataTableExtensionHost({ tableId: 'customers.todos.list', source: 'components/CustomerTodosTable.tsx' }),\n companyForm: crudFormExtensionHost({ entityId: 'customers.company', spotId: 'crud-form:customers.company', source: 'backend/customers/companies-v2/[id]/page.tsx' }),\n dealForm: crudFormExtensionHost({ entityId: 'customers.deal', spotId: 'crud-form:customers.deal', source: 'backend/customers/deals/[id]/page.tsx' }),\n personForm: crudFormExtensionHost({ entityId: 'customers.person', spotId: 'crud-form:customers.person', source: 'backend/customers/people-v2/[id]/page.tsx' }),\n calendarEventTypes: injectionExtensionHost({\n family: 'generic',\n spotId: 'calendar:customers.event-types',\n supported: ['headless-widget', 'registry-contribution'],\n dataContract: 'customers.calendar.event-types.v1',\n source: 'lib/calendar/eventTypeResolver.ts',\n }),\n calendarEventTypePanel: componentExtensionHost({\n componentId: 'section:customers.calendar-event-editor.type-panel',\n propsContract: 'customers.calendar.event-type-panel.v1',\n source: 'components/calendar/editor/EventTypePanel.tsx',\n }),\n companyHeader: detailHost('detail:customers.company:header', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyStatusBadges: detailHost('detail:customers.company:status-badges', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyFooter: detailHost('detail:customers.company:footer', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyLegacyDetails: detailHost('customers.company.detail:details', 'backend/customers/companies/[id]/page.tsx'),\n dealHeader: detailHost('detail:customers.deal:header', 'backend/customers/deals/[id]/page.tsx'),\n dealStatusBadges: detailHost('detail:customers.deal:status-badges', 'backend/customers/deals/[id]/page.tsx'),\n dealFooter: detailHost('detail:customers.deal:footer', 'backend/customers/deals/[id]/page.tsx'),\n personHeader: detailHost('detail:customers.person:header', 'backend/customers/people-v2/[id]/page.tsx'),\n personStatusBadges: detailHost('detail:customers.person:status-badges', 'backend/customers/people-v2/[id]/page.tsx'),\n personFooter: detailHost('detail:customers.person:footer', 'backend/customers/people-v2/[id]/page.tsx'),\n personLegacyDetails: detailHost('customers.person.detail:details', 'backend/customers/people/[id]/page.tsx'),\n },\n})\n\nexport default extensionPoints\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,aAAa,CAAC,QAAgB,
|
|
4
|
+
"sourcesContent": ["import {\n componentExtensionHost,\n crudFormExtensionHost,\n dataTableExtensionHost,\n defineModuleExtensionPoints,\n injectionExtensionHost,\n} from '@open-mercato/shared/modules/widgets/extension-points'\n\nconst detailHost = (spotId: string, source: string, dataContract?: string) => injectionExtensionHost({\n family: 'detail',\n spotId,\n supported: ['render-widget'],\n contextContract: 'customers.detail.v1',\n dataContract,\n source,\n})\n\nexport const extensionPoints = defineModuleExtensionPoints({\n moduleId: 'customers',\n hosts: {\n companiesTable: dataTableExtensionHost({ tableId: 'customers.companies.list', source: 'backend/customers/companies/page.tsx' }),\n dealsTable: dataTableExtensionHost({ tableId: 'customers.deals.list', source: 'backend/customers/deals/page.tsx' }),\n peopleTable: dataTableExtensionHost({ tableId: 'customers.people.list', source: 'backend/customers/people/page.tsx' }),\n todosTable: dataTableExtensionHost({ tableId: 'customers.todos.list', source: 'components/CustomerTodosTable.tsx' }),\n companyForm: crudFormExtensionHost({ entityId: 'customers.company', spotId: 'crud-form:customers.company', source: ['backend/customers/companies-v2/[id]/page.tsx', 'backend/customers/companies/create/page.tsx'] }),\n dealForm: crudFormExtensionHost({ entityId: 'customers.deal', spotId: 'crud-form:customers.deal', source: ['backend/customers/deals/[id]/page.tsx', 'components/detail/create/CreateDealForm.tsx', 'backend/customers/deals/pipeline/components/QuickDealDialog.tsx'] }),\n personForm: crudFormExtensionHost({ entityId: 'customers.person', spotId: 'crud-form:customers.person', source: ['backend/customers/people-v2/[id]/page.tsx', 'backend/customers/people/create/page.tsx', 'components/detail/CreatePersonDialog.tsx'] }),\n calendarEventTypes: injectionExtensionHost({\n family: 'generic',\n spotId: 'calendar:customers.event-types',\n supported: ['headless-widget', 'registry-contribution'],\n dataContract: 'customers.calendar.event-types.v1',\n source: 'lib/calendar/eventTypeResolver.ts',\n }),\n calendarEventTypePanel: componentExtensionHost({\n componentId: 'section:customers.calendar-event-editor.type-panel',\n propsContract: 'customers.calendar.event-type-panel.v1',\n source: 'components/calendar/editor/EventTypePanel.tsx',\n }),\n companyHeaderActions: detailHost('detail:customers.company:header-actions', 'backend/customers/companies-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n companySidebar: detailHost('detail:customers.company:sidebar', 'backend/customers/companies-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n companyTabs: detailHost('detail:customers.company:tabs', 'backend/customers/companies-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n companyHeader: detailHost('detail:customers.company:header', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyStatusBadges: detailHost('detail:customers.company:status-badges', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyFooter: detailHost('detail:customers.company:footer', 'backend/customers/companies-v2/[id]/page.tsx'),\n companyLegacyDetails: detailHost('customers.company.detail:details', 'backend/customers/companies/[id]/page.tsx'),\n dealHeaderActions: detailHost('detail:customers.deal:header-actions', 'backend/customers/deals/[id]/page.tsx', 'customers.detail.overview.v1'),\n dealSidebar: detailHost('detail:customers.deal:sidebar', 'backend/customers/deals/[id]/page.tsx', 'customers.detail.overview.v1'),\n dealTabs: detailHost('detail:customers.deal:tabs', 'backend/customers/deals/[id]/hooks/useDealInjectedTabs.tsx', 'customers.detail.overview.v1'),\n dealHeader: detailHost('detail:customers.deal:header', 'backend/customers/deals/[id]/page.tsx'),\n dealStatusBadges: detailHost('detail:customers.deal:status-badges', 'backend/customers/deals/[id]/page.tsx'),\n dealFooter: detailHost('detail:customers.deal:footer', 'backend/customers/deals/[id]/page.tsx'),\n personHeaderActions: detailHost('detail:customers.person:header-actions', 'backend/customers/people-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n personSidebar: detailHost('detail:customers.person:sidebar', 'backend/customers/people-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n personTabs: detailHost('detail:customers.person:tabs', 'backend/customers/people-v2/[id]/page.tsx', 'customers.detail.overview.v1'),\n personHeader: detailHost('detail:customers.person:header', 'backend/customers/people-v2/[id]/page.tsx'),\n personStatusBadges: detailHost('detail:customers.person:status-badges', 'backend/customers/people-v2/[id]/page.tsx'),\n personFooter: detailHost('detail:customers.person:footer', 'backend/customers/people-v2/[id]/page.tsx'),\n personLegacyDetails: detailHost('customers.person.detail:details', 'backend/customers/people/[id]/page.tsx'),\n },\n})\n\nexport default extensionPoints\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,aAAa,CAAC,QAAgB,QAAgB,iBAA0B,uBAAuB;AAAA,EACnG,QAAQ;AAAA,EACR;AAAA,EACA,WAAW,CAAC,eAAe;AAAA,EAC3B,iBAAiB;AAAA,EACjB;AAAA,EACA;AACF,CAAC;AAEM,MAAM,kBAAkB,4BAA4B;AAAA,EACzD,UAAU;AAAA,EACV,OAAO;AAAA,IACL,gBAAgB,uBAAuB,EAAE,SAAS,4BAA4B,QAAQ,uCAAuC,CAAC;AAAA,IAC9H,YAAY,uBAAuB,EAAE,SAAS,wBAAwB,QAAQ,mCAAmC,CAAC;AAAA,IAClH,aAAa,uBAAuB,EAAE,SAAS,yBAAyB,QAAQ,oCAAoC,CAAC;AAAA,IACrH,YAAY,uBAAuB,EAAE,SAAS,wBAAwB,QAAQ,oCAAoC,CAAC;AAAA,IACnH,aAAa,sBAAsB,EAAE,UAAU,qBAAqB,QAAQ,+BAA+B,QAAQ,CAAC,gDAAgD,6CAA6C,EAAE,CAAC;AAAA,IACpN,UAAU,sBAAsB,EAAE,UAAU,kBAAkB,QAAQ,4BAA4B,QAAQ,CAAC,yCAAyC,+CAA+C,iEAAiE,EAAE,CAAC;AAAA,IACvQ,YAAY,sBAAsB,EAAE,UAAU,oBAAoB,QAAQ,8BAA8B,QAAQ,CAAC,6CAA6C,4CAA4C,0CAA0C,EAAE,CAAC;AAAA,IACvP,oBAAoB,uBAAuB;AAAA,MACzC,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,WAAW,CAAC,mBAAmB,uBAAuB;AAAA,MACtD,cAAc;AAAA,MACd,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,wBAAwB,uBAAuB;AAAA,MAC7C,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,sBAAsB,WAAW,2CAA2C,gDAAgD,8BAA8B;AAAA,IAC1J,gBAAgB,WAAW,oCAAoC,gDAAgD,8BAA8B;AAAA,IAC7I,aAAa,WAAW,iCAAiC,gDAAgD,8BAA8B;AAAA,IACvI,eAAe,WAAW,mCAAmC,8CAA8C;AAAA,IAC3G,qBAAqB,WAAW,0CAA0C,8CAA8C;AAAA,IACxH,eAAe,WAAW,mCAAmC,8CAA8C;AAAA,IAC3G,sBAAsB,WAAW,oCAAoC,2CAA2C;AAAA,IAChH,mBAAmB,WAAW,wCAAwC,yCAAyC,8BAA8B;AAAA,IAC7I,aAAa,WAAW,iCAAiC,yCAAyC,8BAA8B;AAAA,IAChI,UAAU,WAAW,8BAA8B,8DAA8D,8BAA8B;AAAA,IAC/I,YAAY,WAAW,gCAAgC,uCAAuC;AAAA,IAC9F,kBAAkB,WAAW,uCAAuC,uCAAuC;AAAA,IAC3G,YAAY,WAAW,gCAAgC,uCAAuC;AAAA,IAC9F,qBAAqB,WAAW,0CAA0C,6CAA6C,8BAA8B;AAAA,IACrJ,eAAe,WAAW,mCAAmC,6CAA6C,8BAA8B;AAAA,IACxI,YAAY,WAAW,gCAAgC,6CAA6C,8BAA8B;AAAA,IAClI,cAAc,WAAW,kCAAkC,2CAA2C;AAAA,IACtG,oBAAoB,WAAW,yCAAyC,2CAA2C;AAAA,IACnH,cAAc,WAAW,kCAAkC,2CAA2C;AAAA,IACtG,qBAAqB,WAAW,mCAAmC,wCAAwC;AAAA,EAC7G;AACF,CAAC;AAED,IAAO,2BAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getEnrichersForEntity } from "@open-mercato/shared/lib/crud/enricher-registry";
|
|
2
|
+
import { applyResponseEnricherToRecord } from "@open-mercato/shared/lib/crud/enricher-runner";
|
|
3
|
+
import { extractResponseEnricherNamespaces } from "@open-mercato/shared/lib/crud/response-enricher-namespaces";
|
|
4
|
+
async function enrichCustomerDetailResponse(payload, recordKey, context) {
|
|
5
|
+
if (getEnrichersForEntity(`customers.${recordKey}`).length === 0) return payload;
|
|
6
|
+
const clonedPayload = structuredClone(payload);
|
|
7
|
+
const mainRecord = clonedPayload[recordKey];
|
|
8
|
+
if (!context.tenantId || !context.organizationId || !mainRecord || typeof mainRecord !== "object" || Array.isArray(mainRecord)) {
|
|
9
|
+
return clonedPayload;
|
|
10
|
+
}
|
|
11
|
+
let userFeatures;
|
|
12
|
+
try {
|
|
13
|
+
const rbac = context.container.resolve("rbacService");
|
|
14
|
+
if (rbac?.getGrantedFeatures) {
|
|
15
|
+
userFeatures = await rbac.getGrantedFeatures(context.auth.sub, {
|
|
16
|
+
tenantId: context.tenantId,
|
|
17
|
+
organizationId: context.organizationId
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
userFeatures = void 0;
|
|
22
|
+
}
|
|
23
|
+
const record = mainRecord;
|
|
24
|
+
const result = await applyResponseEnricherToRecord(structuredClone(record), `customers.${recordKey}`, {
|
|
25
|
+
tenantId: context.tenantId,
|
|
26
|
+
organizationId: context.organizationId,
|
|
27
|
+
userId: context.auth.sub,
|
|
28
|
+
em: context.em,
|
|
29
|
+
container: context.container,
|
|
30
|
+
userFeatures
|
|
31
|
+
});
|
|
32
|
+
const namespaces = extractResponseEnricherNamespaces(result.record);
|
|
33
|
+
return { ...clonedPayload, ...namespaces, [recordKey]: { ...record, ...namespaces } };
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
enrichCustomerDetailResponse
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=detailResponseEnrichment.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/customers/lib/detailResponseEnrichment.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { getEnrichersForEntity } from '@open-mercato/shared/lib/crud/enricher-registry'\nimport { applyResponseEnricherToRecord } from '@open-mercato/shared/lib/crud/enricher-runner'\nimport { extractResponseEnricherNamespaces } from '@open-mercato/shared/lib/crud/response-enricher-namespaces'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\ntype DetailRecordKey = 'person' | 'company' | 'deal'\n\ntype DetailEnrichmentContext = {\n auth: NonNullable<AuthContext>\n container: AppContainer\n em: EntityManager\n tenantId: string | null\n organizationId: string | null\n}\n\nexport async function enrichCustomerDetailResponse<T extends Record<string, unknown>>(\n payload: T,\n recordKey: DetailRecordKey,\n context: DetailEnrichmentContext,\n): Promise<T> {\n if (getEnrichersForEntity(`customers.${recordKey}`).length === 0) return payload\n const clonedPayload = structuredClone(payload)\n const mainRecord = clonedPayload[recordKey]\n if (!context.tenantId || !context.organizationId || !mainRecord || typeof mainRecord !== 'object' || Array.isArray(mainRecord)) {\n return clonedPayload\n }\n let userFeatures: string[] | undefined\n try {\n const rbac = context.container.resolve<RbacService>('rbacService')\n if (rbac?.getGrantedFeatures) {\n userFeatures = await rbac.getGrantedFeatures(context.auth.sub, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n } catch {\n userFeatures = undefined\n }\n const record = mainRecord as Record<string, unknown>\n const result = await applyResponseEnricherToRecord(structuredClone(record), `customers.${recordKey}`, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: context.auth.sub,\n em: context.em,\n container: context.container,\n userFeatures,\n })\n const namespaces = extractResponseEnricherNamespaces(result.record)\n return { ...clonedPayload, ...namespaces, [recordKey]: { ...record, ...namespaces } }\n}\n"],
|
|
5
|
+
"mappings": "AAGA,SAAS,6BAA6B;AACtC,SAAS,qCAAqC;AAC9C,SAAS,yCAAyC;AAalD,eAAsB,6BACpB,SACA,WACA,SACY;AACZ,MAAI,sBAAsB,aAAa,SAAS,EAAE,EAAE,WAAW,EAAG,QAAO;AACzE,QAAM,gBAAgB,gBAAgB,OAAO;AAC7C,QAAM,aAAa,cAAc,SAAS;AAC1C,MAAI,CAAC,QAAQ,YAAY,CAAC,QAAQ,kBAAkB,CAAC,cAAc,OAAO,eAAe,YAAY,MAAM,QAAQ,UAAU,GAAG;AAC9H,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,QAAQ,UAAU,QAAqB,aAAa;AACjE,QAAI,MAAM,oBAAoB;AAC5B,qBAAe,MAAM,KAAK,mBAAmB,QAAQ,KAAK,KAAK;AAAA,QAC7D,UAAU,QAAQ;AAAA,QAClB,gBAAgB,QAAQ;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AACN,mBAAe;AAAA,EACjB;AACA,QAAM,SAAS;AACf,QAAM,SAAS,MAAM,8BAA8B,gBAAgB,MAAM,GAAG,aAAa,SAAS,IAAI;AAAA,IACpG,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,IACxB,QAAQ,QAAQ,KAAK;AAAA,IACrB,IAAI,QAAQ;AAAA,IACZ,WAAW,QAAQ;AAAA,IACnB;AAAA,EACF,CAAC;AACD,QAAM,aAAa,kCAAkC,OAAO,MAAM;AAClE,SAAO,EAAE,GAAG,eAAe,GAAG,YAAY,CAAC,SAAS,GAAG,EAAE,GAAG,QAAQ,GAAG,WAAW,EAAE;AACtF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
+
import { resolveUserDataSubject } from "@open-mercato/core/modules/auth/lib/userDataSubject";
|
|
5
7
|
import { DashboardLayout } from "@open-mercato/core/modules/dashboards/data/entities";
|
|
6
8
|
import { dashboardLayoutItemPatchSchema } from "@open-mercato/core/modules/dashboards/data/validators";
|
|
7
9
|
import {
|
|
@@ -41,13 +43,18 @@ async function PATCH(req, ctx) {
|
|
|
41
43
|
const { resolve } = container;
|
|
42
44
|
const em = resolve("em");
|
|
43
45
|
const rbac = resolve("rbacService");
|
|
46
|
+
const userId = await resolveUserDataSubject(em, auth);
|
|
47
|
+
if (!userId) {
|
|
48
|
+
const { translate } = await resolveTranslations();
|
|
49
|
+
return NextResponse.json({ error: translate("api.errors.forbidden", "Forbidden") }, { status: 403 });
|
|
50
|
+
}
|
|
44
51
|
const scope = {
|
|
45
|
-
userId
|
|
52
|
+
userId,
|
|
46
53
|
tenantId: auth.tenantId ?? null,
|
|
47
54
|
organizationId: auth.orgId ?? null
|
|
48
55
|
};
|
|
49
56
|
const canConfigure = await rbac.userHasAllFeatures(
|
|
50
|
-
|
|
57
|
+
auth.sub,
|
|
51
58
|
["dashboards.configure"],
|
|
52
59
|
{ tenantId: scope.tenantId, organizationId: scope.organizationId }
|
|
53
60
|
);
|
|
@@ -57,7 +64,7 @@ async function PATCH(req, ctx) {
|
|
|
57
64
|
const guardResult = await validateCrudMutationGuard(container, {
|
|
58
65
|
tenantId: scope.tenantId ?? "",
|
|
59
66
|
organizationId: scope.organizationId,
|
|
60
|
-
userId:
|
|
67
|
+
userId: auth.sub,
|
|
61
68
|
resourceKind: RESOURCE_KIND,
|
|
62
69
|
resourceId: layoutItemId,
|
|
63
70
|
operation: "update",
|
|
@@ -92,7 +99,7 @@ async function PATCH(req, ctx) {
|
|
|
92
99
|
await runCrudMutationGuardAfterSuccess(container, {
|
|
93
100
|
tenantId: scope.tenantId ?? "",
|
|
94
101
|
organizationId: scope.organizationId,
|
|
95
|
-
userId:
|
|
102
|
+
userId: auth.sub,
|
|
96
103
|
resourceKind: RESOURCE_KIND,
|
|
97
104
|
resourceId: layoutItemId,
|
|
98
105
|
operation: "update",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/api/layout/%5BitemId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardLayout } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { dashboardLayoutItemPatchSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardsOkSchema,\n dashboardLayoutItemUpdateSchema,\n} from '../../openapi'\n\nconst DEFAULT_SIZE = 'md'\nconst RESOURCE_KIND = 'dashboards.layout'\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['dashboards.configure'] },\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { itemId?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const layoutItemId = ctx.params?.itemId\n if (!layoutItemId) return NextResponse.json({ error: 'Missing layout item id' }, { status: 400 })\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return NextResponse.json({ error: 'Invalid payload' }, { status: 400 })\n }\n const parsed = dashboardLayoutItemPatchSchema.safeParse({ ...(body as Record<string, unknown>), id: layoutItemId })\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n\n const
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,sCAAsC;AAC/C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAEf,MAAM,WAAW;AAAA,EACtB,OAAO,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACxE;AAEA,eAAsB,MAAM,KAAc,KAAuC;AAC/E,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9E,QAAM,eAAe,IAAI,QAAQ;AACjC,MAAI,CAAC,aAAc,QAAO,aAAa,KAAK,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEhG,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AACA,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;AAC5D,WAAO,aAAa,KAAK,EAAE,OAAO,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxE;AACA,QAAM,SAAS,+BAA+B,UAAU,EAAE,GAAI,MAAkC,IAAI,aAAa,CAAC;AAClH,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrG;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,KAAK,QAAQ,IAAI;AACvB,QAAM,OAAO,QAAQ,aAAa;AAElC,QAAM,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveUserDataSubject } from '@open-mercato/core/modules/auth/lib/userDataSubject'\nimport { DashboardLayout } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { dashboardLayoutItemPatchSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardsOkSchema,\n dashboardLayoutItemUpdateSchema,\n} from '../../openapi'\n\nconst DEFAULT_SIZE = 'md'\nconst RESOURCE_KIND = 'dashboards.layout'\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['dashboards.configure'] },\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { itemId?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const layoutItemId = ctx.params?.itemId\n if (!layoutItemId) return NextResponse.json({ error: 'Missing layout item id' }, { status: 400 })\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return NextResponse.json({ error: 'Invalid payload' }, { status: 400 })\n }\n const parsed = dashboardLayoutItemPatchSchema.safeParse({ ...(body as Record<string, unknown>), id: layoutItemId })\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n\n const userId = await resolveUserDataSubject(em, auth)\n if (!userId) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ error: translate('api.errors.forbidden', 'Forbidden') }, { status: 403 })\n }\n\n const scope = {\n userId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n\n const canConfigure = await rbac.userHasAllFeatures(\n auth.sub,\n ['dashboards.configure'],\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n if (!canConfigure) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: scope.tenantId ?? '',\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: RESOURCE_KIND,\n resourceId: layoutItemId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { id: layoutItemId, size: parsed.data.size, settings: parsed.data.settings },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const layout = await em.findOne(DashboardLayout, {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n if (!layout) {\n return NextResponse.json({ error: 'Layout not found' }, { status: 404 })\n }\n\n const idx = layout.layoutJson.findIndex((item: { id?: string | null }) => item?.id === layoutItemId)\n if (idx === -1) {\n return NextResponse.json({ error: 'Layout item not found' }, { status: 404 })\n }\n\n const current = layout.layoutJson[idx]\n layout.layoutJson[idx] = {\n ...current,\n size: parsed.data.size ?? current.size ?? DEFAULT_SIZE,\n settings: parsed.data.settings ?? current.settings,\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: scope.tenantId ?? '',\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: RESOURCE_KIND,\n resourceId: layoutItemId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n}\n\nconst layoutItemParamsSchema = z.object({\n itemId: z.string().uuid(),\n})\n\nconst layoutItemPatchDoc: OpenApiMethodDoc = {\n summary: 'Update a dashboard layout item',\n description: 'Adjusts the size or settings for a single widget within the dashboard layout.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: dashboardLayoutItemUpdateSchema,\n description: 'Payload containing the new size or settings for the widget.',\n },\n responses: [\n { status: 200, description: 'Layout item updated.', schema: dashboardsOkSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or missing item id', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Missing dashboards.configure feature', schema: dashboardsErrorSchema },\n { status: 404, description: 'Item not found', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Update dashboard layout item',\n pathParams: layoutItemParamsSchema,\n methods: {\n PATCH: layoutItemPatchDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,6 +9,8 @@ import { resolveAllowedWidgetIds } from "@open-mercato/core/modules/dashboards/l
|
|
|
9
9
|
import { authorizeFeatures } from "@open-mercato/shared/security/featurePolicy";
|
|
10
10
|
import { User } from "@open-mercato/core/modules/auth/data/entities";
|
|
11
11
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
12
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
13
|
+
import { resolveUserDataSubject } from "@open-mercato/core/modules/auth/lib/userDataSubject";
|
|
12
14
|
import {
|
|
13
15
|
runCrudMutationGuardAfterSuccess,
|
|
14
16
|
validateCrudMutationGuard
|
|
@@ -61,13 +63,17 @@ async function GET(req) {
|
|
|
61
63
|
const container = await createRequestContainer();
|
|
62
64
|
const em = container.resolve("em").fork({ clear: true, freshEventManager: true, useContext: true });
|
|
63
65
|
const rbac = container.resolve("rbacService");
|
|
64
|
-
const
|
|
66
|
+
const userId = await resolveUserDataSubject(em, auth);
|
|
67
|
+
if (!userId) {
|
|
68
|
+
const { translate } = await resolveTranslations();
|
|
69
|
+
return NextResponse.json({ error: translate("api.errors.forbidden", "Forbidden") }, { status: 403 });
|
|
70
|
+
}
|
|
65
71
|
const scope = {
|
|
66
|
-
userId
|
|
72
|
+
userId,
|
|
67
73
|
tenantId: auth.tenantId ?? null,
|
|
68
74
|
organizationId: auth.orgId ?? null
|
|
69
75
|
};
|
|
70
|
-
const effectiveFeatures = await rbac.getEffectiveFeatures(
|
|
76
|
+
const effectiveFeatures = await rbac.getEffectiveFeatures(auth.sub, {
|
|
71
77
|
tenantId: scope.tenantId,
|
|
72
78
|
organizationId: scope.organizationId
|
|
73
79
|
});
|
|
@@ -98,7 +104,7 @@ async function GET(req) {
|
|
|
98
104
|
size: widget.metadata.defaultSize ?? DEFAULT_SIZE,
|
|
99
105
|
settings: widget.metadata.defaultSettings ?? void 0
|
|
100
106
|
}));
|
|
101
|
-
if (hasRegisteredWidgets) {
|
|
107
|
+
if (hasRegisteredWidgets && !auth.isApiKey) {
|
|
102
108
|
layout = em.create(DashboardLayout, {
|
|
103
109
|
userId: scope.userId,
|
|
104
110
|
tenantId: scope.tenantId,
|
|
@@ -108,7 +114,7 @@ async function GET(req) {
|
|
|
108
114
|
em.persist(layout);
|
|
109
115
|
hasChanged = true;
|
|
110
116
|
}
|
|
111
|
-
} else if (hasRegisteredWidgets) {
|
|
117
|
+
} else if (hasRegisteredWidgets || auth.isApiKey) {
|
|
112
118
|
const existingLayout = layout;
|
|
113
119
|
const filtered = items.filter((item) => allowedIds.includes(item.widgetId));
|
|
114
120
|
if (filtered.length !== items.length) {
|
|
@@ -119,10 +125,10 @@ async function GET(req) {
|
|
|
119
125
|
if (existingLayout.layoutJson.length !== items.length || items.some((item, idx) => existingLayout.layoutJson[idx]?.id !== item.id)) {
|
|
120
126
|
hasChanged = true;
|
|
121
127
|
}
|
|
122
|
-
existingLayout.layoutJson = items;
|
|
128
|
+
if (!auth.isApiKey) existingLayout.layoutJson = items;
|
|
123
129
|
layout = existingLayout;
|
|
124
130
|
}
|
|
125
|
-
if (hasChanged) {
|
|
131
|
+
if (hasChanged && !auth.isApiKey) {
|
|
126
132
|
await em.flush();
|
|
127
133
|
}
|
|
128
134
|
const canConfigure = authorizeFeatures(["dashboards.configure"], {
|
|
@@ -189,13 +195,18 @@ async function PUT(req) {
|
|
|
189
195
|
const { resolve } = container;
|
|
190
196
|
const em = resolve("em").fork({ clear: true, freshEventManager: true, useContext: true });
|
|
191
197
|
const rbac = resolve("rbacService");
|
|
198
|
+
const userId = await resolveUserDataSubject(em, auth);
|
|
199
|
+
if (!userId) {
|
|
200
|
+
const { translate } = await resolveTranslations();
|
|
201
|
+
return NextResponse.json({ error: translate("api.errors.forbidden", "Forbidden") }, { status: 403 });
|
|
202
|
+
}
|
|
192
203
|
const scope = {
|
|
193
|
-
userId
|
|
204
|
+
userId,
|
|
194
205
|
tenantId: auth.tenantId ?? null,
|
|
195
206
|
organizationId: auth.orgId ?? null
|
|
196
207
|
};
|
|
197
208
|
const canConfigure = await rbac.userHasAllFeatures(
|
|
198
|
-
|
|
209
|
+
auth.sub,
|
|
199
210
|
["dashboards.configure"],
|
|
200
211
|
{ tenantId: scope.tenantId, organizationId: scope.organizationId }
|
|
201
212
|
);
|
|
@@ -205,7 +216,7 @@ async function PUT(req) {
|
|
|
205
216
|
const guardResult = await validateCrudMutationGuard(container, {
|
|
206
217
|
tenantId: scope.tenantId ?? "",
|
|
207
218
|
organizationId: scope.organizationId,
|
|
208
|
-
userId:
|
|
219
|
+
userId: auth.sub,
|
|
209
220
|
resourceKind: RESOURCE_KIND,
|
|
210
221
|
resourceId: scope.userId,
|
|
211
222
|
operation: "update",
|
|
@@ -220,7 +231,7 @@ async function PUT(req) {
|
|
|
220
231
|
if (widgets.length === 0) {
|
|
221
232
|
return NextResponse.json({ error: "Widget registry unavailable" }, { status: 503 });
|
|
222
233
|
}
|
|
223
|
-
const effectiveFeatures = await rbac.getEffectiveFeatures(
|
|
234
|
+
const effectiveFeatures = await rbac.getEffectiveFeatures(auth.sub, {
|
|
224
235
|
tenantId: scope.tenantId,
|
|
225
236
|
organizationId: scope.organizationId
|
|
226
237
|
});
|
|
@@ -266,7 +277,7 @@ async function PUT(req) {
|
|
|
266
277
|
await runCrudMutationGuardAfterSuccess(container, {
|
|
267
278
|
tenantId: scope.tenantId ?? "",
|
|
268
279
|
organizationId: scope.organizationId,
|
|
269
|
-
userId:
|
|
280
|
+
userId: auth.sub,
|
|
270
281
|
resourceKind: RESOURCE_KIND,
|
|
271
282
|
resourceId: scope.userId,
|
|
272
283
|
operation: "update",
|
|
@@ -289,7 +300,8 @@ const layoutGetDoc = {
|
|
|
289
300
|
}
|
|
290
301
|
],
|
|
291
302
|
errors: [
|
|
292
|
-
{ status: 401, description: "Authentication required", schema: dashboardsErrorSchema }
|
|
303
|
+
{ status: 401, description: "Authentication required", schema: dashboardsErrorSchema },
|
|
304
|
+
{ status: 403, description: "No user binding in the authenticated scope", schema: dashboardsErrorSchema }
|
|
293
305
|
]
|
|
294
306
|
};
|
|
295
307
|
const layoutPutDoc = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/dashboards/api/layout/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { randomUUID } from 'node:crypto'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardLayout } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { dashboardLayoutSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { resolveAllowedWidgetIds } from '@open-mercato/core/modules/dashboards/lib/access'\nimport { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardsOkSchema,\n dashboardLayoutStateSchema,\n} from '../openapi'\n\nconst DEFAULT_SIZE = 'md'\nconst RESOURCE_KIND = 'dashboards.layout'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dashboards.view'] },\n PUT: { requireAuth: true, requireFeatures: ['dashboards.configure'] },\n}\n\ntype LayoutScope = {\n userId: string\n tenantId: string | null\n organizationId: string | null\n}\n\nasync function loadScopeLayout(em: any, scope: LayoutScope): Promise<DashboardLayout | null> {\n return await em.findOne(DashboardLayout, {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n}\n\nfunction normalizeLayoutItems(items: any[]) {\n const list = Array.isArray(items) ? items : []\n const seenIds = new Set<string>()\n const sanitized = list\n .filter((item) => item && typeof item === 'object')\n .map((item) => ({\n id: String(item.id),\n widgetId: String(item.widgetId),\n order: Number.isInteger(item.order) ? Number(item.order) : undefined,\n priority: Number.isInteger(item.priority) ? Number(item.priority) : undefined,\n size: typeof item.size === 'string' ? item.size : undefined,\n settings: item.settings,\n }))\n .filter((item) => {\n if (!item.id || !item.widgetId) return false\n if (seenIds.has(item.id)) return false\n seenIds.add(item.id)\n return true\n })\n .sort((a, b) => {\n const aOrder = a.order ?? a.priority ?? 0\n const bOrder = b.order ?? b.priority ?? 0\n return aOrder - bOrder\n })\n .map((item, idx) => ({ ...item, order: idx, priority: idx }))\n return sanitized\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n // Use a fresh fork to avoid carrying over pending entities from other operations\n const em = (container.resolve('em') as any).fork({ clear: true, freshEventManager: true, useContext: true })\n const rbac = container.resolve('rbacService') as any\n const url = new URL(req.url)\n\n const scope: LayoutScope = {\n userId: String(auth.sub),\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n\n const effectiveFeatures = await rbac.getEffectiveFeatures(scope.userId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n const widgets = await loadAllWidgets()\n const allowedIds = await resolveAllowedWidgetIds(\n em,\n {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n features: effectiveFeatures,\n isSuperAdmin: false,\n },\n widgets,\n )\n const allowedWidgets = widgets.filter((w) => allowedIds.includes(w.metadata.id))\n\n // An empty widget registry means the module registry has not been populated yet\n // (a boot race), not that this app has no widgets. Every write below is derived\n // from it, so a read request must persist nothing until it recovers \u2014 otherwise a\n // restart-recoverable glitch is written into saved layouts for good (#5041).\n // Note this keys off the registry, not off `allowedIds`: a user whose allowlist is\n // legitimately empty on a healthy registry is still pruned and seeded as before.\n const hasRegisteredWidgets = widgets.length > 0\n\n let layout = await loadScopeLayout(em, scope)\n let items = layout ? normalizeLayoutItems(layout.layoutJson) : []\n let hasChanged = false\n\n if (!layout) {\n const defaults = allowedWidgets.filter((widget) => widget.metadata.defaultEnabled)\n items = defaults.map((widget, index) => ({\n id: randomUUID(),\n widgetId: widget.metadata.id,\n order: index,\n priority: index,\n size: widget.metadata.defaultSize ?? DEFAULT_SIZE,\n settings: widget.metadata.defaultSettings ?? undefined,\n }))\n if (hasRegisteredWidgets) {\n layout = em.create(DashboardLayout, {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n layoutJson: items,\n })\n em.persist(layout)\n hasChanged = true\n }\n } else if (hasRegisteredWidgets) {\n const existingLayout = layout\n const filtered = items.filter((item) => allowedIds.includes(item.widgetId))\n if (filtered.length !== items.length) {\n hasChanged = true\n items = filtered\n }\n items = items.map((item, index) => (item.order !== index || item.priority !== index ? { ...item, order: index, priority: index } : item))\n if (\n existingLayout.layoutJson.length !== items.length ||\n items.some((item, idx) => existingLayout.layoutJson[idx]?.id !== item.id)\n ) {\n hasChanged = true\n }\n existingLayout.layoutJson = items\n layout = existingLayout\n }\n\n if (hasChanged) {\n await em.flush()\n }\n\n const canConfigure = authorizeFeatures(['dashboards.configure'], {\n grantedFeatures: effectiveFeatures,\n })\n\n let userEmail: string | null = null\n let userName: string | null = null\n let userLabel: string | null = null\n const user = await findOneWithDecryption(\n em,\n User,\n { id: scope.userId, deletedAt: null },\n undefined,\n { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },\n )\n if (user) {\n userName = user.name?.trim() ?? null\n userEmail = user.email ?? null\n userLabel = (userName && userName.length > 0 ? userName : userEmail) ?? null\n }\n if (!userLabel) {\n userLabel = scope.userId\n }\n\n const response = {\n layout: { items },\n allowedWidgetIds: allowedIds,\n canConfigure,\n context: {\n ...scope,\n userName,\n userEmail,\n userLabel,\n },\n widgets: allowedWidgets.map((widget) => ({\n id: widget.metadata.id,\n title: widget.metadata.title,\n description: widget.metadata.description ?? null,\n defaultSize: widget.metadata.defaultSize ?? DEFAULT_SIZE,\n defaultEnabled: !!widget.metadata.defaultEnabled,\n defaultSettings: widget.metadata.defaultSettings ?? null,\n features: widget.metadata.features ?? [],\n moduleId: widget.moduleId,\n icon: widget.metadata.icon ?? null,\n loaderKey: widget.key,\n supportsRefresh: !!widget.metadata.supportsRefresh,\n })),\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = dashboardLayoutSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid layout payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = (resolve('em') as any).fork({ clear: true, freshEventManager: true, useContext: true })\n const rbac = resolve('rbacService') as any\n\n const scope: LayoutScope = {\n userId: String(auth.sub),\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n\n const canConfigure = await rbac.userHasAllFeatures(\n scope.userId,\n ['dashboards.configure'],\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n if (!canConfigure) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: scope.tenantId ?? '',\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: RESOURCE_KIND,\n resourceId: scope.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { items: parsed.data.items },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const widgets = await loadAllWidgets()\n // The allowlist below is derived from the registry, and this handler persists the\n // filtered result unconditionally. An empty registry \u2014 the boot race behind #5041 \u2014\n // would drop every submitted item and save `[]` while answering `{ ok: true }`, so a\n // stale tab reordering a widget could erase the layout it just rendered. Fail the\n // write instead: the save is explicit, so the client must learn it did not happen\n // and retry, rather than be told a wipe succeeded.\n if (widgets.length === 0) {\n return NextResponse.json({ error: 'Widget registry unavailable' }, { status: 503 })\n }\n const effectiveFeatures = await rbac.getEffectiveFeatures(scope.userId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n const allowedIds = await resolveAllowedWidgetIds(\n em,\n {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n features: effectiveFeatures,\n isSuperAdmin: false,\n },\n widgets,\n )\n const allowedSet = new Set(allowedIds)\n\n const payloadItems = parsed.data.items\n const sanitized = payloadItems\n .map((item, index) => ({\n id: item.id,\n widgetId: item.widgetId,\n order: index,\n priority: index,\n size: item.size ?? DEFAULT_SIZE,\n settings: item.settings,\n }))\n .filter((item) => allowedSet.has(item.widgetId))\n\n const uniqueIds = new Set(sanitized.map((item) => item.id))\n if (uniqueIds.size !== sanitized.length) {\n return NextResponse.json({ error: 'Layout item IDs must be unique' }, { status: 400 })\n }\n\n let layout = await loadScopeLayout(em, scope)\n if (!layout) {\n layout = em.create(DashboardLayout, {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n layoutJson: sanitized,\n })\n em.persist(layout)\n } else {\n layout.layoutJson = sanitized\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: scope.tenantId ?? '',\n organizationId: scope.organizationId,\n userId: scope.userId,\n resourceKind: RESOURCE_KIND,\n resourceId: scope.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n}\n\nconst layoutGetDoc: OpenApiMethodDoc = {\n summary: 'Load the current dashboard layout',\n description: 'Returns the saved widget layout together with the widgets the current user is allowed to place.',\n tags: [dashboardsTag],\n responses: [\n {\n status: 200,\n description: 'Current dashboard layout and available widgets.',\n schema: dashboardLayoutStateSchema,\n },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n ],\n}\n\nconst layoutPutDoc: OpenApiMethodDoc = {\n summary: 'Persist dashboard layout changes',\n description: 'Saves the provided widget ordering, sizes, and settings for the current user.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: dashboardLayoutSchema,\n description: 'List of dashboard widgets with ordering, sizing, and settings.',\n },\n responses: [\n { status: 200, description: 'Layout updated successfully.', schema: dashboardsOkSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid layout payload', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Missing dashboards.configure feature', schema: dashboardsErrorSchema },\n { status: 503, description: 'Widget registry unavailable \u2014 the layout was not saved', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage personal dashboard layout',\n methods: {\n GET: layoutGetDoc,\n PUT: layoutPutDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,+BAA+B;AACxC,SAAS,yBAAyB;AAClC,SAAS,YAAY;AACrB,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAEf,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAQA,eAAe,gBAAgB,IAAS,OAAqD;AAC3F,SAAO,MAAM,GAAG,QAAQ,iBAAiB;AAAA,IACvC,QAAQ,MAAM;AAAA,IACd,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AACH;AAEA,SAAS,qBAAqB,OAAc;AAC1C,QAAM,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;AAC7C,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,YAAY,KACf,OAAO,CAAC,SAAS,QAAQ,OAAO,SAAS,QAAQ,EACjD,IAAI,CAAC,UAAU;AAAA,IACd,IAAI,OAAO,KAAK,EAAE;AAAA,IAClB,UAAU,OAAO,KAAK,QAAQ;AAAA,IAC9B,OAAO,OAAO,UAAU,KAAK,KAAK,IAAI,OAAO,KAAK,KAAK,IAAI;AAAA,IAC3D,UAAU,OAAO,UAAU,KAAK,QAAQ,IAAI,OAAO,KAAK,QAAQ,IAAI;AAAA,IACpE,MAAM,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AAAA,IAClD,UAAU,KAAK;AAAA,EACjB,EAAE,EACD,OAAO,CAAC,SAAS;AAChB,QAAI,CAAC,KAAK,MAAM,CAAC,KAAK,SAAU,QAAO;AACvC,QAAI,QAAQ,IAAI,KAAK,EAAE,EAAG,QAAO;AACjC,YAAQ,IAAI,KAAK,EAAE;AACnB,WAAO;AAAA,EACT,CAAC,EACA,KAAK,CAAC,GAAG,MAAM;AACd,UAAM,SAAS,EAAE,SAAS,EAAE,YAAY;AACxC,UAAM,SAAS,EAAE,SAAS,EAAE,YAAY;AACxC,WAAO,SAAS;AAAA,EAClB,CAAC,EACA,IAAI,CAAC,MAAM,SAAS,EAAE,GAAG,MAAM,OAAO,KAAK,UAAU,IAAI,EAAE;AAC9D,SAAO;AACT;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE9E,QAAM,YAAY,MAAM,uBAAuB;AAE/C,QAAM,KAAM,UAAU,QAAQ,IAAI,EAAU,KAAK,EAAE,OAAO,MAAM,mBAAmB,MAAM,YAAY,KAAK,CAAC;AAC3G,QAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,QAAM,MAAM,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { randomUUID } from 'node:crypto'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardLayout } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { dashboardLayoutSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { resolveAllowedWidgetIds } from '@open-mercato/core/modules/dashboards/lib/access'\nimport { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveUserDataSubject } from '@open-mercato/core/modules/auth/lib/userDataSubject'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardsOkSchema,\n dashboardLayoutStateSchema,\n} from '../openapi'\n\nconst DEFAULT_SIZE = 'md'\nconst RESOURCE_KIND = 'dashboards.layout'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dashboards.view'] },\n PUT: { requireAuth: true, requireFeatures: ['dashboards.configure'] },\n}\n\ntype LayoutScope = {\n userId: string\n tenantId: string | null\n organizationId: string | null\n}\n\nasync function loadScopeLayout(em: any, scope: LayoutScope): Promise<DashboardLayout | null> {\n return await em.findOne(DashboardLayout, {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n}\n\nfunction normalizeLayoutItems(items: any[]) {\n const list = Array.isArray(items) ? items : []\n const seenIds = new Set<string>()\n const sanitized = list\n .filter((item) => item && typeof item === 'object')\n .map((item) => ({\n id: String(item.id),\n widgetId: String(item.widgetId),\n order: Number.isInteger(item.order) ? Number(item.order) : undefined,\n priority: Number.isInteger(item.priority) ? Number(item.priority) : undefined,\n size: typeof item.size === 'string' ? item.size : undefined,\n settings: item.settings,\n }))\n .filter((item) => {\n if (!item.id || !item.widgetId) return false\n if (seenIds.has(item.id)) return false\n seenIds.add(item.id)\n return true\n })\n .sort((a, b) => {\n const aOrder = a.order ?? a.priority ?? 0\n const bOrder = b.order ?? b.priority ?? 0\n return aOrder - bOrder\n })\n .map((item, idx) => ({ ...item, order: idx, priority: idx }))\n return sanitized\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const container = await createRequestContainer()\n // Use a fresh fork to avoid carrying over pending entities from other operations\n const em = (container.resolve('em') as any).fork({ clear: true, freshEventManager: true, useContext: true })\n const rbac = container.resolve('rbacService') as any\n const userId = await resolveUserDataSubject(em, auth)\n if (!userId) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ error: translate('api.errors.forbidden', 'Forbidden') }, { status: 403 })\n }\n\n const scope: LayoutScope = {\n userId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n\n const effectiveFeatures = await rbac.getEffectiveFeatures(auth.sub, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n const widgets = await loadAllWidgets()\n const allowedIds = await resolveAllowedWidgetIds(\n em,\n {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n features: effectiveFeatures,\n isSuperAdmin: false,\n },\n widgets,\n )\n const allowedWidgets = widgets.filter((w) => allowedIds.includes(w.metadata.id))\n\n // An empty widget registry means the module registry has not been populated yet\n // (a boot race), not that this app has no widgets. Every write below is derived\n // from it, so a read request must persist nothing until it recovers \u2014 otherwise a\n // restart-recoverable glitch is written into saved layouts for good (#5041).\n // Note this keys off the registry, not off `allowedIds`: a user whose allowlist is\n // legitimately empty on a healthy registry is still pruned and seeded as before.\n const hasRegisteredWidgets = widgets.length > 0\n\n let layout = await loadScopeLayout(em, scope)\n let items = layout ? normalizeLayoutItems(layout.layoutJson) : []\n let hasChanged = false\n\n if (!layout) {\n const defaults = allowedWidgets.filter((widget) => widget.metadata.defaultEnabled)\n items = defaults.map((widget, index) => ({\n id: randomUUID(),\n widgetId: widget.metadata.id,\n order: index,\n priority: index,\n size: widget.metadata.defaultSize ?? DEFAULT_SIZE,\n settings: widget.metadata.defaultSettings ?? undefined,\n }))\n if (hasRegisteredWidgets && !auth.isApiKey) {\n layout = em.create(DashboardLayout, {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n layoutJson: items,\n })\n em.persist(layout)\n hasChanged = true\n }\n } else if (hasRegisteredWidgets || auth.isApiKey) {\n const existingLayout = layout\n const filtered = items.filter((item) => allowedIds.includes(item.widgetId))\n if (filtered.length !== items.length) {\n hasChanged = true\n items = filtered\n }\n items = items.map((item, index) => (item.order !== index || item.priority !== index ? { ...item, order: index, priority: index } : item))\n if (\n existingLayout.layoutJson.length !== items.length ||\n items.some((item, idx) => existingLayout.layoutJson[idx]?.id !== item.id)\n ) {\n hasChanged = true\n }\n if (!auth.isApiKey) existingLayout.layoutJson = items\n layout = existingLayout\n }\n\n if (hasChanged && !auth.isApiKey) {\n await em.flush()\n }\n\n const canConfigure = authorizeFeatures(['dashboards.configure'], {\n grantedFeatures: effectiveFeatures,\n })\n\n let userEmail: string | null = null\n let userName: string | null = null\n let userLabel: string | null = null\n const user = await findOneWithDecryption(\n em,\n User,\n { id: scope.userId, deletedAt: null },\n undefined,\n { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },\n )\n if (user) {\n userName = user.name?.trim() ?? null\n userEmail = user.email ?? null\n userLabel = (userName && userName.length > 0 ? userName : userEmail) ?? null\n }\n if (!userLabel) {\n userLabel = scope.userId\n }\n\n const response = {\n layout: { items },\n allowedWidgetIds: allowedIds,\n canConfigure,\n context: {\n ...scope,\n userName,\n userEmail,\n userLabel,\n },\n widgets: allowedWidgets.map((widget) => ({\n id: widget.metadata.id,\n title: widget.metadata.title,\n description: widget.metadata.description ?? null,\n defaultSize: widget.metadata.defaultSize ?? DEFAULT_SIZE,\n defaultEnabled: !!widget.metadata.defaultEnabled,\n defaultSettings: widget.metadata.defaultSettings ?? null,\n features: widget.metadata.features ?? [],\n moduleId: widget.moduleId,\n icon: widget.metadata.icon ?? null,\n loaderKey: widget.key,\n supportsRefresh: !!widget.metadata.supportsRefresh,\n })),\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = dashboardLayoutSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid layout payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = (resolve('em') as any).fork({ clear: true, freshEventManager: true, useContext: true })\n const rbac = resolve('rbacService') as any\n\n const userId = await resolveUserDataSubject(em, auth)\n if (!userId) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ error: translate('api.errors.forbidden', 'Forbidden') }, { status: 403 })\n }\n\n const scope: LayoutScope = {\n userId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n\n const canConfigure = await rbac.userHasAllFeatures(\n auth.sub,\n ['dashboards.configure'],\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n if (!canConfigure) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: scope.tenantId ?? '',\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: RESOURCE_KIND,\n resourceId: scope.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { items: parsed.data.items },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const widgets = await loadAllWidgets()\n // The allowlist below is derived from the registry, and this handler persists the\n // filtered result unconditionally. An empty registry \u2014 the boot race behind #5041 \u2014\n // would drop every submitted item and save `[]` while answering `{ ok: true }`, so a\n // stale tab reordering a widget could erase the layout it just rendered. Fail the\n // write instead: the save is explicit, so the client must learn it did not happen\n // and retry, rather than be told a wipe succeeded.\n if (widgets.length === 0) {\n return NextResponse.json({ error: 'Widget registry unavailable' }, { status: 503 })\n }\n const effectiveFeatures = await rbac.getEffectiveFeatures(auth.sub, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n const allowedIds = await resolveAllowedWidgetIds(\n em,\n {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n features: effectiveFeatures,\n isSuperAdmin: false,\n },\n widgets,\n )\n const allowedSet = new Set(allowedIds)\n\n const payloadItems = parsed.data.items\n const sanitized = payloadItems\n .map((item, index) => ({\n id: item.id,\n widgetId: item.widgetId,\n order: index,\n priority: index,\n size: item.size ?? DEFAULT_SIZE,\n settings: item.settings,\n }))\n .filter((item) => allowedSet.has(item.widgetId))\n\n const uniqueIds = new Set(sanitized.map((item) => item.id))\n if (uniqueIds.size !== sanitized.length) {\n return NextResponse.json({ error: 'Layout item IDs must be unique' }, { status: 400 })\n }\n\n let layout = await loadScopeLayout(em, scope)\n if (!layout) {\n layout = em.create(DashboardLayout, {\n userId: scope.userId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n layoutJson: sanitized,\n })\n em.persist(layout)\n } else {\n layout.layoutJson = sanitized\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: scope.tenantId ?? '',\n organizationId: scope.organizationId,\n userId: auth.sub,\n resourceKind: RESOURCE_KIND,\n resourceId: scope.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n}\n\nconst layoutGetDoc: OpenApiMethodDoc = {\n summary: 'Load the current dashboard layout',\n description: 'Returns the saved widget layout together with the widgets the current user is allowed to place.',\n tags: [dashboardsTag],\n responses: [\n {\n status: 200,\n description: 'Current dashboard layout and available widgets.',\n schema: dashboardLayoutStateSchema,\n },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'No user binding in the authenticated scope', schema: dashboardsErrorSchema },\n ],\n}\n\nconst layoutPutDoc: OpenApiMethodDoc = {\n summary: 'Persist dashboard layout changes',\n description: 'Saves the provided widget ordering, sizes, and settings for the current user.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: dashboardLayoutSchema,\n description: 'List of dashboard widgets with ordering, sizing, and settings.',\n },\n responses: [\n { status: 200, description: 'Layout updated successfully.', schema: dashboardsOkSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid layout payload', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Missing dashboards.configure feature', schema: dashboardsErrorSchema },\n { status: 503, description: 'Widget registry unavailable \u2014 the layout was not saved', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage personal dashboard layout',\n methods: {\n GET: layoutGetDoc,\n PUT: layoutPutDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,7 +13,7 @@ function SyncStatusBadge({ status, lastSynced }) {
|
|
|
13
13
|
const t = useT();
|
|
14
14
|
const config = SYNC_STATUS_STYLES[status];
|
|
15
15
|
const label = t(config.label, status);
|
|
16
|
-
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
16
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
|
|
17
17
|
/* @__PURE__ */ jsx("span", { className: cn("inline-block size-1.5 rounded-full", config.dot), "aria-hidden": "true" }),
|
|
18
18
|
/* @__PURE__ */ jsx("span", { children: label }),
|
|
19
19
|
lastSynced && /* @__PURE__ */ jsx("span", { title: lastSynced, children: new Date(lastSynced).toLocaleDateString() })
|
|
@@ -36,16 +36,16 @@ function ExternalIdsWidget({ data }) {
|
|
|
36
36
|
{
|
|
37
37
|
className: "flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2",
|
|
38
38
|
children: [
|
|
39
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2
|
|
39
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: [
|
|
40
40
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: getIntegrationTitle(integrationId) }),
|
|
41
|
-
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground", children: mapping.externalId }),
|
|
41
|
+
/* @__PURE__ */ jsx("code", { className: "min-w-0 max-w-full break-all rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground", children: mapping.externalId }),
|
|
42
42
|
mapping.externalUrl && /* @__PURE__ */ jsx(
|
|
43
43
|
"a",
|
|
44
44
|
{
|
|
45
45
|
href: mapping.externalUrl,
|
|
46
46
|
target: "_blank",
|
|
47
47
|
rel: "noopener noreferrer",
|
|
48
|
-
className: "text-muted-foreground hover:text-foreground transition-colors",
|
|
48
|
+
className: "shrink-0 text-muted-foreground hover:text-foreground transition-colors",
|
|
49
49
|
"aria-label": t("integrations.externalIds.openExternal", "Open in external system"),
|
|
50
50
|
children: /* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
51
51
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/integrations/widgets/injection/external-ids/widget.client.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { ExternalIdMapping } from '@open-mercato/shared/modules/integrations/types'\nimport { getIntegrationTitle } from '@open-mercato/shared/modules/integrations/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype IntegrationsData = Record<string, ExternalIdMapping>\n\nconst SYNC_STATUS_STYLES: Record<ExternalIdMapping['syncStatus'], { dot: string; label: string }> = {\n synced: { dot: 'bg-status-success-icon', label: 'integrations.syncStatus.synced' },\n pending: { dot: 'bg-status-warning-icon', label: 'integrations.syncStatus.pending' },\n error: { dot: 'bg-status-error-icon', label: 'integrations.syncStatus.error' },\n not_synced: { dot: 'bg-status-neutral-icon', label: 'integrations.syncStatus.notSynced' },\n}\n\nfunction SyncStatusBadge({ status, lastSynced }: { status: ExternalIdMapping['syncStatus']; lastSynced?: string }) {\n const t = useT()\n const config = SYNC_STATUS_STYLES[status]\n const label = t(config.label, status)\n\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <span className={cn('inline-block size-1.5 rounded-full', config.dot)} aria-hidden=\"true\" />\n <span>{label}</span>\n {lastSynced && (\n <span title={lastSynced}>\n {new Date(lastSynced).toLocaleDateString()}\n </span>\n )}\n </span>\n )\n}\n\nfunction ExternalLinkIcon() {\n return (\n <svg className=\"size-3.5\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path d=\"M6.5 3.5H3a1 1 0 0 0-1 1V13a1 1 0 0 0 1 1h8.5a1 1 0 0 0 1-1V9.5\" />\n <path d=\"M9.5 2h4.5v4.5M14 2 7.5 8.5\" />\n </svg>\n )\n}\n\nexport default function ExternalIdsWidget({ data }: InjectionWidgetComponentProps) {\n const t = useT()\n const integrations = (data as Record<string, unknown>)?._integrations as IntegrationsData | undefined\n\n if (!integrations || Object.keys(integrations).length === 0) return null\n\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <h3 className=\"mb-3 text-sm font-medium\">\n {t('integrations.externalIds.title', 'External IDs')}\n </h3>\n <div className=\"space-y-2\">\n {Object.entries(integrations).map(([integrationId, mapping]) => (\n <div\n key={integrationId}\n className=\"flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2\"\n >\n <div className=\"flex items-center gap-2
|
|
5
|
-
"mappings": ";AAwBI,SACE,KADF;AAnBJ,SAAS,2BAA2B;AACpC,SAAS,YAAY;AACrB,SAAS,UAAU;AAInB,MAAM,qBAA8F;AAAA,EAClG,QAAQ,EAAE,KAAK,0BAA0B,OAAO,iCAAiC;AAAA,EACjF,SAAS,EAAE,KAAK,0BAA0B,OAAO,kCAAkC;AAAA,EACnF,OAAO,EAAE,KAAK,wBAAwB,OAAO,gCAAgC;AAAA,EAC7E,YAAY,EAAE,KAAK,0BAA0B,OAAO,oCAAoC;AAC1F;AAEA,SAAS,gBAAgB,EAAE,QAAQ,WAAW,GAAqE;AACjH,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,mBAAmB,MAAM;AACxC,QAAM,QAAQ,EAAE,OAAO,OAAO,MAAM;AAEpC,SACE,qBAAC,UAAK,WAAU,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport type { ExternalIdMapping } from '@open-mercato/shared/modules/integrations/types'\nimport { getIntegrationTitle } from '@open-mercato/shared/modules/integrations/types'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype IntegrationsData = Record<string, ExternalIdMapping>\n\nconst SYNC_STATUS_STYLES: Record<ExternalIdMapping['syncStatus'], { dot: string; label: string }> = {\n synced: { dot: 'bg-status-success-icon', label: 'integrations.syncStatus.synced' },\n pending: { dot: 'bg-status-warning-icon', label: 'integrations.syncStatus.pending' },\n error: { dot: 'bg-status-error-icon', label: 'integrations.syncStatus.error' },\n not_synced: { dot: 'bg-status-neutral-icon', label: 'integrations.syncStatus.notSynced' },\n}\n\nfunction SyncStatusBadge({ status, lastSynced }: { status: ExternalIdMapping['syncStatus']; lastSynced?: string }) {\n const t = useT()\n const config = SYNC_STATUS_STYLES[status]\n const label = t(config.label, status)\n\n return (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <span className={cn('inline-block size-1.5 rounded-full', config.dot)} aria-hidden=\"true\" />\n <span>{label}</span>\n {lastSynced && (\n <span title={lastSynced}>\n {new Date(lastSynced).toLocaleDateString()}\n </span>\n )}\n </span>\n )\n}\n\nfunction ExternalLinkIcon() {\n return (\n <svg className=\"size-3.5\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path d=\"M6.5 3.5H3a1 1 0 0 0-1 1V13a1 1 0 0 0 1 1h8.5a1 1 0 0 0 1-1V9.5\" />\n <path d=\"M9.5 2h4.5v4.5M14 2 7.5 8.5\" />\n </svg>\n )\n}\n\nexport default function ExternalIdsWidget({ data }: InjectionWidgetComponentProps) {\n const t = useT()\n const integrations = (data as Record<string, unknown>)?._integrations as IntegrationsData | undefined\n\n if (!integrations || Object.keys(integrations).length === 0) return null\n\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <h3 className=\"mb-3 text-sm font-medium\">\n {t('integrations.externalIds.title', 'External IDs')}\n </h3>\n <div className=\"space-y-2\">\n {Object.entries(integrations).map(([integrationId, mapping]) => (\n <div\n key={integrationId}\n className=\"flex items-center justify-between gap-2 rounded-md bg-muted/50 px-3 py-2\"\n >\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\">\n <span className=\"text-sm font-medium truncate\">\n {getIntegrationTitle(integrationId)}\n </span>\n <code className=\"min-w-0 max-w-full break-all rounded bg-muted px-1.5 py-0.5 text-xs font-mono text-muted-foreground\">\n {mapping.externalId}\n </code>\n {mapping.externalUrl && (\n <a\n href={mapping.externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"shrink-0 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={t('integrations.externalIds.openExternal', 'Open in external system')}\n >\n <ExternalLinkIcon />\n </a>\n )}\n </div>\n <SyncStatusBadge status={mapping.syncStatus} lastSynced={mapping.lastSyncedAt} />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\n"],
|
|
5
|
+
"mappings": ";AAwBI,SACE,KADF;AAnBJ,SAAS,2BAA2B;AACpC,SAAS,YAAY;AACrB,SAAS,UAAU;AAInB,MAAM,qBAA8F;AAAA,EAClG,QAAQ,EAAE,KAAK,0BAA0B,OAAO,iCAAiC;AAAA,EACjF,SAAS,EAAE,KAAK,0BAA0B,OAAO,kCAAkC;AAAA,EACnF,OAAO,EAAE,KAAK,wBAAwB,OAAO,gCAAgC;AAAA,EAC7E,YAAY,EAAE,KAAK,0BAA0B,OAAO,oCAAoC;AAC1F;AAEA,SAAS,gBAAgB,EAAE,QAAQ,WAAW,GAAqE;AACjH,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,mBAAmB,MAAM;AACxC,QAAM,QAAQ,EAAE,OAAO,OAAO,MAAM;AAEpC,SACE,qBAAC,UAAK,WAAU,yEACd;AAAA,wBAAC,UAAK,WAAW,GAAG,sCAAsC,OAAO,GAAG,GAAG,eAAY,QAAO;AAAA,IAC1F,oBAAC,UAAM,iBAAM;AAAA,IACZ,cACC,oBAAC,UAAK,OAAO,YACV,cAAI,KAAK,UAAU,EAAE,mBAAmB,GAC3C;AAAA,KAEJ;AAEJ;AAEA,SAAS,mBAAmB;AAC1B,SACE,qBAAC,SAAI,WAAU,YAAW,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAC1F;AAAA,wBAAC,UAAK,GAAE,mEAAkE;AAAA,IAC1E,oBAAC,UAAK,GAAE,+BAA8B;AAAA,KACxC;AAEJ;AAEe,SAAR,kBAAmC,EAAE,KAAK,GAAkC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,eAAgB,MAAkC;AAExD,MAAI,CAAC,gBAAgB,OAAO,KAAK,YAAY,EAAE,WAAW,EAAG,QAAO;AAEpE,SACE,qBAAC,SAAI,WAAU,iCACb;AAAA,wBAAC,QAAG,WAAU,4BACX,YAAE,kCAAkC,cAAc,GACrD;AAAA,IACA,oBAAC,SAAI,WAAU,aACZ,iBAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,eAAe,OAAO,MACxD;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,+BAAC,SAAI,WAAU,oDACb;AAAA,gCAAC,UAAK,WAAU,gCACb,8BAAoB,aAAa,GACpC;AAAA,YACA,oBAAC,UAAK,WAAU,uGACb,kBAAQ,YACX;AAAA,YACC,QAAQ,eACP;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM,QAAQ;AAAA,gBACd,QAAO;AAAA,gBACP,KAAI;AAAA,gBACJ,WAAU;AAAA,gBACV,cAAY,EAAE,yCAAyC,yBAAyB;AAAA,gBAEhF,8BAAC,oBAAiB;AAAA;AAAA,YACpB;AAAA,aAEJ;AAAA,UACA,oBAAC,mBAAgB,QAAQ,QAAQ,YAAY,YAAY,QAAQ,cAAc;AAAA;AAAA;AAAA,MAtB1E;AAAA,IAuBP,CACD,GACH;AAAA,KACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.10.1-develop.
|
|
3
|
+
"version": "0.10.1-develop.7763.1.caea616ebf",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -266,16 +266,16 @@
|
|
|
266
266
|
"zod": "^4.4.3"
|
|
267
267
|
},
|
|
268
268
|
"peerDependencies": {
|
|
269
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
270
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
271
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
269
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7763.1.caea616ebf",
|
|
270
|
+
"@open-mercato/shared": "0.10.1-develop.7763.1.caea616ebf",
|
|
271
|
+
"@open-mercato/ui": "0.10.1-develop.7763.1.caea616ebf",
|
|
272
272
|
"react": "^19.0.0",
|
|
273
273
|
"react-dom": "^19.0.0"
|
|
274
274
|
},
|
|
275
275
|
"devDependencies": {
|
|
276
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
277
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
278
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
276
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7763.1.caea616ebf",
|
|
277
|
+
"@open-mercato/shared": "0.10.1-develop.7763.1.caea616ebf",
|
|
278
|
+
"@open-mercato/ui": "0.10.1-develop.7763.1.caea616ebf",
|
|
279
279
|
"@testing-library/dom": "^10.4.1",
|
|
280
280
|
"@testing-library/jest-dom": "^7.0.1",
|
|
281
281
|
"@testing-library/react": "^16.3.3",
|