@open-mercato/core 0.4.5 → 0.4.6-canary-c15f8174eb
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/README.md +27 -3
- package/dist/modules/messages/acl.js +7 -7
- package/dist/modules/messages/acl.js.map +2 -2
- package/dist/modules/messages/backend/messages/[id]/page.js +4 -2
- package/dist/modules/messages/backend/messages/[id]/page.js.map +2 -2
- package/dist/modules/messages/backend/messages/[id]/page.meta.js +0 -1
- package/dist/modules/messages/backend/messages/[id]/page.meta.js.map +2 -2
- package/dist/modules/messages/backend/messages/compose/page.js +4 -2
- package/dist/modules/messages/backend/messages/compose/page.js.map +2 -2
- package/dist/modules/messages/backend/messages/compose/page.meta.js +0 -1
- package/dist/modules/messages/backend/messages/compose/page.meta.js.map +2 -2
- package/dist/modules/messages/backend/page.js +4 -2
- package/dist/modules/messages/backend/page.js.map +2 -2
- package/dist/modules/messages/backend/page.meta.js +0 -1
- package/dist/modules/messages/backend/page.meta.js.map +2 -2
- package/dist/modules/messages/components/ComposeMessagePageClient.js +16 -1
- package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +18 -1
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +35 -4
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/lib/access.js +36 -0
- package/dist/modules/messages/lib/access.js.map +7 -0
- package/package.json +4 -3
- package/src/modules/messages/acl.ts +7 -7
- package/src/modules/messages/backend/messages/[id]/page.meta.ts +0 -1
- package/src/modules/messages/backend/messages/[id]/page.tsx +4 -2
- package/src/modules/messages/backend/messages/compose/page.meta.ts +0 -1
- package/src/modules/messages/backend/messages/compose/page.tsx +4 -2
- package/src/modules/messages/backend/page.meta.ts +0 -1
- package/src/modules/messages/backend/page.tsx +5 -2
- package/src/modules/messages/components/ComposeMessagePageClient.tsx +16 -1
- package/src/modules/messages/components/MessageDetailPageClient.tsx +18 -1
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +46 -4
- package/src/modules/messages/i18n/de.json +2 -0
- package/src/modules/messages/i18n/en.json +2 -0
- package/src/modules/messages/i18n/es.json +2 -0
- package/src/modules/messages/i18n/pl.json +2 -0
- package/src/modules/messages/lib/access.ts +46 -0
package/README.md
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
@open-mercato/core
|
|
1
|
+
# @open-mercato/core 🧠
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Build production-grade business modules on top of the Open Mercato platform.
|
|
4
|
+
|
|
5
|
+
`@open-mercato/core` is the business engine: catalog, customers, sales, auth, workflows, settings, and module infrastructure.
|
|
6
|
+
|
|
7
|
+
## Why this package ✨
|
|
8
|
+
|
|
9
|
+
- 🧩 Ready-made domain modules for real business apps
|
|
10
|
+
- 🔐 Built-in ACL, tenancy, and secure-by-default conventions
|
|
11
|
+
- ⚡ Command-based architecture for robust writes and undo support
|
|
12
|
+
- 🛠 Extensible module system with API/page auto-discovery
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add @open-mercato/core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Learn more
|
|
21
|
+
|
|
22
|
+
- GitHub (package): https://github.com/open-mercato/open-mercato/tree/main/packages/core
|
|
23
|
+
- GitHub (project): https://github.com/open-mercato/open-mercato
|
|
24
|
+
- Docs: https://docs.openmercato.com
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
MIT
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const features = [
|
|
2
|
-
"messages.view",
|
|
3
|
-
"messages.compose",
|
|
4
|
-
"messages.attach",
|
|
5
|
-
"messages.attach_files",
|
|
6
|
-
"messages.email",
|
|
7
|
-
"messages.actions",
|
|
8
|
-
"messages.manage"
|
|
2
|
+
{ id: "messages.view", title: "View messages", module: "messages" },
|
|
3
|
+
{ id: "messages.compose", title: "Compose messages", module: "messages" },
|
|
4
|
+
{ id: "messages.attach", title: "Attach records to messages", module: "messages" },
|
|
5
|
+
{ id: "messages.attach_files", title: "Attach files to messages", module: "messages" },
|
|
6
|
+
{ id: "messages.email", title: "Send messages via email", module: "messages" },
|
|
7
|
+
{ id: "messages.actions", title: "Execute message actions", module: "messages" },
|
|
8
|
+
{ id: "messages.manage", title: "Manage all messages", module: "messages" }
|
|
9
9
|
];
|
|
10
10
|
var acl_default = features;
|
|
11
11
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/messages/acl.ts"],
|
|
4
|
-
"sourcesContent": ["export const features = [\n 'messages.view',\n 'messages.compose',\n 'messages.attach',\n 'messages.attach_files',\n 'messages.email',\n 'messages.actions',\n 'messages.manage',\n]\n\nexport default features\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB;AAAA,
|
|
4
|
+
"sourcesContent": ["export const features = [\n { id: 'messages.view', title: 'View messages', module: 'messages' },\n { id: 'messages.compose', title: 'Compose messages', module: 'messages' },\n { id: 'messages.attach', title: 'Attach records to messages', module: 'messages' },\n { id: 'messages.attach_files', title: 'Attach files to messages', module: 'messages' },\n { id: 'messages.email', title: 'Send messages via email', module: 'messages' },\n { id: 'messages.actions', title: 'Execute message actions', module: 'messages' },\n { id: 'messages.manage', title: 'Manage all messages', module: 'messages' },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,iBAAiB,OAAO,iBAAiB,QAAQ,WAAW;AAAA,EAClE,EAAE,IAAI,oBAAoB,OAAO,oBAAoB,QAAQ,WAAW;AAAA,EACxE,EAAE,IAAI,mBAAmB,OAAO,8BAA8B,QAAQ,WAAW;AAAA,EACjF,EAAE,IAAI,yBAAyB,OAAO,4BAA4B,QAAQ,WAAW;AAAA,EACrF,EAAE,IAAI,kBAAkB,OAAO,2BAA2B,QAAQ,WAAW;AAAA,EAC7E,EAAE,IAAI,oBAAoB,OAAO,2BAA2B,QAAQ,WAAW;AAAA,EAC/E,EAAE,IAAI,mBAAmB,OAAO,uBAAuB,QAAQ,WAAW;AAC5E;AAEA,IAAO,cAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
3
3
|
import { MessageDetailPageClient } from "../../../components/MessageDetailPageClient.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { resolveCanViewMessagesForCurrentUser } from "../../../lib/access.js";
|
|
5
|
+
async function MessageDetailPage({ params }) {
|
|
6
|
+
const canViewMessages = await resolveCanViewMessagesForCurrentUser();
|
|
7
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(MessageDetailPageClient, { id: params.id, canViewMessages }) }) });
|
|
6
8
|
}
|
|
7
9
|
export {
|
|
8
10
|
MessageDetailPage as default
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/backend/messages/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { MessageDetailPageClient } from '../../../components/MessageDetailPageClient'\n\nexport default function MessageDetailPage({ params }: { params: { id: string } }) {\n return (\n <Page>\n <PageBody>\n <MessageDetailPageClient id={params.id} />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { MessageDetailPageClient } from '../../../components/MessageDetailPageClient'\nimport { resolveCanViewMessagesForCurrentUser } from '../../../lib/access'\n\nexport default async function MessageDetailPage({ params }: { params: { id: string } }) {\n const canViewMessages = await resolveCanViewMessagesForCurrentUser()\n return (\n <Page>\n <PageBody>\n <MessageDetailPageClient id={params.id} canViewMessages={canViewMessages} />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "AASQ;AATR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,+BAA+B;AACxC,SAAS,4CAA4C;AAErD,eAAO,kBAAyC,EAAE,OAAO,GAA+B;AACtF,QAAM,kBAAkB,MAAM,qCAAqC;AACnE,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,2BAAwB,IAAI,OAAO,IAAI,iBAAkC,GAC5E,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/backend/messages/%5Bid%5D/page.meta.ts"],
|
|
4
|
-
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,
|
|
4
|
+
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n pageTitle: 'Message details',\n pageTitleKey: 'messages.nav.detail',\n pageGroup: 'Messages',\n pageGroupKey: 'messages.nav.group',\n navHidden: true,\n breadcrumb: [\n { label: 'Messages', labelKey: 'messages.nav.inbox', href: '/backend/messages' },\n ],\n} as const\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,YAAY,UAAU,sBAAsB,MAAM,oBAAoB;AAAA,EACjF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
3
3
|
import { ComposeMessagePageClient } from "../../../components/ComposeMessagePageClient.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { resolveCanViewMessagesForCurrentUser } from "../../../lib/access.js";
|
|
5
|
+
async function ComposeMessagePage() {
|
|
6
|
+
const canViewMessages = await resolveCanViewMessagesForCurrentUser();
|
|
7
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ComposeMessagePageClient, { canViewMessages }) }) });
|
|
6
8
|
}
|
|
7
9
|
export {
|
|
8
10
|
ComposeMessagePage as default
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/backend/messages/compose/page.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { ComposeMessagePageClient } from '../../../components/ComposeMessagePageClient'\n\nexport default function ComposeMessagePage() {\n return (\n <Page>\n <PageBody>\n <ComposeMessagePageClient />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { ComposeMessagePageClient } from '../../../components/ComposeMessagePageClient'\nimport { resolveCanViewMessagesForCurrentUser } from '../../../lib/access'\n\nexport default async function ComposeMessagePage() {\n const canViewMessages = await resolveCanViewMessagesForCurrentUser()\n return (\n <Page>\n <PageBody>\n <ComposeMessagePageClient canViewMessages={canViewMessages} />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "AASQ;AATR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gCAAgC;AACzC,SAAS,4CAA4C;AAErD,eAAO,qBAA4C;AACjD,QAAM,kBAAkB,MAAM,qCAAqC;AACnE,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,4BAAyB,iBAAkC,GAC9D,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/backend/messages/compose/page.meta.ts"],
|
|
4
|
-
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,
|
|
4
|
+
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n pageTitle: 'Compose message',\n pageTitleKey: 'messages.nav.compose',\n pageGroup: 'Messages',\n pageGroupKey: 'messages.nav.group',\n navHidden: true,\n breadcrumb: [\n { label: 'Messages', labelKey: 'messages.nav.inbox', href: '/backend/messages' },\n { label: 'Compose', labelKey: 'messages.nav.compose' },\n ],\n} as const\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,YAAY,UAAU,sBAAsB,MAAM,oBAAoB;AAAA,IAC/E,EAAE,OAAO,WAAW,UAAU,uBAAuB;AAAA,EACvD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
3
3
|
import { MessagesInboxPageClient } from "../components/MessagesInboxPageClient.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { resolveCanViewMessagesForCurrentUser } from "../lib/access.js";
|
|
5
|
+
async function MessagesInboxPage() {
|
|
6
|
+
const canViewMessages = await resolveCanViewMessagesForCurrentUser();
|
|
7
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(MessagesInboxPageClient, { canViewMessages }) }) });
|
|
6
8
|
}
|
|
7
9
|
export {
|
|
8
10
|
MessagesInboxPage as default
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/backend/page.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { MessagesInboxPageClient } from '../components/MessagesInboxPageClient'\n\nexport default function MessagesInboxPage() {\n return (\n <Page>\n <PageBody>\n <MessagesInboxPageClient />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { MessagesInboxPageClient } from '../components/MessagesInboxPageClient'\nimport { resolveCanViewMessagesForCurrentUser } from '../lib/access'\n\nexport default async function MessagesInboxPage() {\n const canViewMessages = await resolveCanViewMessagesForCurrentUser()\n\n return (\n <Page>\n <PageBody>\n <MessagesInboxPageClient canViewMessages={canViewMessages} />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "AAUQ;AAVR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,+BAA+B;AACxC,SAAS,4CAA4C;AAErD,eAAO,oBAA2C;AAChD,QAAM,kBAAkB,MAAM,qCAAqC;AAEnE,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,2BAAwB,iBAAkC,GAC7D,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/backend/page.meta.ts"],
|
|
4
|
-
"sourcesContent": ["import React from 'react'\n\nconst mailIcon = React.createElement(\n 'svg',\n {\n width: 16,\n height: 16,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n },\n React.createElement('rect', { x: 2, y: 4, width: 20, height: 16, rx: 2 }),\n React.createElement('path', { d: 'm22 7-8.97 5.7a2 2 0 0 1-2.06 0L2 7' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n
|
|
5
|
-
"mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,WAAW,MAAM;AAAA,EACrB;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AAAA,EACA,MAAM,cAAc,QAAQ,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;AAAA,EACxE,MAAM,cAAc,QAAQ,EAAE,GAAG,sCAAsC,CAAC;AAC1E;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,
|
|
4
|
+
"sourcesContent": ["import React from 'react'\n\nconst mailIcon = React.createElement(\n 'svg',\n {\n width: 16,\n height: 16,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n },\n React.createElement('rect', { x: 2, y: 4, width: 20, height: 16, rx: 2 }),\n React.createElement('path', { d: 'm22 7-8.97 5.7a2 2 0 0 1-2.06 0L2 7' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n pageTitle: 'Messages',\n pageTitleKey: 'messages.nav.inbox',\n pageGroup: 'Messages',\n pageGroupKey: 'messages.nav.group',\n pageOrder: 460,\n icon: mailIcon,\n breadcrumb: [\n { label: 'Messages', labelKey: 'messages.nav.inbox' },\n ],\n} as const\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,WAAW,MAAM;AAAA,EACrB;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AAAA,EACA,MAAM,cAAc,QAAQ,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;AAAA,EACxE,MAAM,cAAc,QAAQ,EAAE,GAAG,sCAAsC,CAAC;AAC1E;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY;AAAA,IACV,EAAE,OAAO,YAAY,UAAU,qBAAqB;AAAA,EACtD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,8 +2,23 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useRouter } from "next/navigation";
|
|
4
4
|
import { MessageComposer } from "@open-mercato/ui/backend/messages";
|
|
5
|
-
|
|
5
|
+
import { ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
|
+
function ComposeMessagePageClient({ canViewMessages = true }) {
|
|
6
8
|
const router = useRouter();
|
|
9
|
+
const t = useT();
|
|
10
|
+
if (!canViewMessages) {
|
|
11
|
+
return /* @__PURE__ */ jsx(
|
|
12
|
+
ErrorMessage,
|
|
13
|
+
{
|
|
14
|
+
label: t("messages.access.disabled.title", "Messages module is disabled for your role."),
|
|
15
|
+
description: t(
|
|
16
|
+
"messages.access.disabled.description",
|
|
17
|
+
"Ask your administrator to enable the required Messages permissions."
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
7
22
|
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
8
23
|
MessageComposer,
|
|
9
24
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/ComposeMessagePageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport { useRouter } from 'next/navigation'\nimport { MessageComposer } from '@open-mercato/ui/backend/messages'\n\nexport function ComposeMessagePageClient() {\n const router = useRouter()\n\n return (\n <div className=\"space-y-4\">\n <MessageComposer\n inline\n variant=\"compose\"\n onCancel={() => {\n router.push('/backend/messages')\n }}\n onSuccess={(result) => {\n router.push('/backend/messages')\n }}\n />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport { useRouter } from 'next/navigation'\nimport { MessageComposer } from '@open-mercato/ui/backend/messages'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport function ComposeMessagePageClient({ canViewMessages = true }: { canViewMessages?: boolean }) {\n const router = useRouter()\n const t = useT()\n\n if (!canViewMessages) {\n return (\n <ErrorMessage\n label={t('messages.access.disabled.title', 'Messages module is disabled for your role.')}\n description={t(\n 'messages.access.disabled.description',\n 'Ask your administrator to enable the required Messages permissions.',\n )}\n />\n )\n }\n\n return (\n <div className=\"space-y-4\">\n <MessageComposer\n inline\n variant=\"compose\"\n onCancel={() => {\n router.push('/backend/messages')\n }}\n onSuccess={(result) => {\n router.push('/backend/messages')\n }}\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAaM;AAXN,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,YAAY;AAEd,SAAS,yBAAyB,EAAE,kBAAkB,KAAK,GAAkC;AAClG,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AAEf,MAAI,CAAC,iBAAiB;AACpB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,kCAAkC,4CAA4C;AAAA,QACvF,aAAa;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAU,aACb;AAAA,IAAC;AAAA;AAAA,MACC,QAAM;AAAA,MACN,SAAQ;AAAA,MACR,UAAU,MAAM;AACd,eAAO,KAAK,mBAAmB;AAAA,MACjC;AAAA,MACA,WAAW,CAAC,WAAW;AACrB,eAAO,KAAK,mBAAmB;AAAA,MACjC;AAAA;AAAA,EACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ import { MessageComposer } from "@open-mercato/ui/backend/messages";
|
|
|
5
5
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
6
6
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
7
7
|
import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
8
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
8
9
|
import {
|
|
9
10
|
getMessageUiComponentRegistry
|
|
10
11
|
} from "./utils/typeUiRegistry.js";
|
|
@@ -123,7 +124,7 @@ function MessageConversationDetailItem({
|
|
|
123
124
|
)
|
|
124
125
|
] });
|
|
125
126
|
}
|
|
126
|
-
function
|
|
127
|
+
function MessageDetailPageClientContent({ id }) {
|
|
127
128
|
const state = useMessageDetails(id);
|
|
128
129
|
const [activeInlineComposer, setActiveInlineComposer] = React.useState(null);
|
|
129
130
|
const inlineComposerContainerRef = React.useRef(null);
|
|
@@ -255,6 +256,22 @@ function MessageDetailPageClient({ id }) {
|
|
|
255
256
|
ConfirmDialogElement
|
|
256
257
|
] });
|
|
257
258
|
}
|
|
259
|
+
function MessageDetailPageClient({ id, canViewMessages = true }) {
|
|
260
|
+
const t = useT();
|
|
261
|
+
if (!canViewMessages) {
|
|
262
|
+
return /* @__PURE__ */ jsx(
|
|
263
|
+
ErrorMessage,
|
|
264
|
+
{
|
|
265
|
+
label: t("messages.access.disabled.title", "Messages module is disabled for your role."),
|
|
266
|
+
description: t(
|
|
267
|
+
"messages.access.disabled.description",
|
|
268
|
+
"Ask your administrator to enable the required Messages permissions."
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return /* @__PURE__ */ jsx(MessageDetailPageClientContent, { id });
|
|
274
|
+
}
|
|
258
275
|
export {
|
|
259
276
|
MessageDetailPageClient
|
|
260
277
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/MessageDetailPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { MessageComposer } from '@open-mercato/ui/backend/messages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport {\n getMessageUiComponentRegistry,\n} from './utils/typeUiRegistry'\nimport {\n MessageDetailActionsSection,\n MessageDetailAttachmentsSection,\n MessageDetailBodySection,\n MessageDetailComposerDialogs,\n MessageDetailDialogs,\n MainMessageHeader,\n MessageListComponent,\n MessageHeader,\n MessageDetailMetaSection,\n MessageDetailObjectsSection,\n} from './message-detail/detail-panels'\nimport { useMessageDetails } from './message-detail/hooks/useMessageDetails'\n\nfunction MessageConversationDetailItem({\n messageId,\n isCollapsible,\n isExpanded,\n onToggle,\n onReply,\n onForward,\n}: {\n messageId: string\n isCollapsible: boolean\n isExpanded: boolean\n onToggle: () => void\n onReply: (messageId: string) => void\n onForward: (messageId: string) => void\n}) {\n const state = useMessageDetails(messageId)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n\n if (state.isLoadingDetail) {\n return (\n <section className=\"py-3\">\n <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n </section>\n )\n }\n\n if (!state.detail) {\n return (\n <section className=\"py-3\">\n <ErrorMessage label={state.loadErrorMessage} />\n </section>\n )\n }\n\n const ContentComponent = state.contentComponentKey\n ? messageUiRegistry.contentComponents[state.contentComponentKey] ?? null\n : null\n const ActionsComponent = state.actionsComponentKey\n ? messageUiRegistry.actionsComponents[state.actionsComponentKey] ?? null\n : null\n\n return (\n <section className=\"py-3\">\n <div className=\"space-y-4\">\n <section className=\"space-y-2 py-2\">\n <MessageHeader\n detail={state.detail}\n updatingState={state.updatingState}\n isArchived={state.isArchived}\n showSubject={false}\n collapseToggle={isCollapsible ? { expanded: isExpanded, onToggle } : undefined}\n onReply={() => onReply(messageId)}\n onForward={() => onForward(messageId)}\n onEdit={() => state.setEditOpen(true)}\n onToggleRead={() => void state.toggleRead()}\n onToggleArchive={() => void state.toggleArchive()}\n onDelete={() => state.setDeleteConfirmationOpen(true)}\n />\n\n <MessageDetailBodySection\n detail={state.detail}\n contentProps={state.contentProps}\n ContentComponent={ContentComponent}\n />\n\n <MessageDetailMetaSection detail={state.detail} />\n </section>\n\n <MessageDetailActionsSection\n detail={state.detail}\n messageActions={state.messageActions}\n executingActionId={state.executingActionId}\n ActionsComponent={ActionsComponent}\n onExecuteActionById={state.handleExecuteActionById}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailObjectsSection\n detail={state.detail}\n objectActionsByObjectId={state.objectActionsByObjectId}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailAttachmentsSection\n attachmentsQuery={state.attachmentsQuery}\n attachments={state.attachments}\n />\n </div>\n\n <MessageDetailComposerDialogs\n id={messageId}\n detail={state.detail}\n attachments={state.attachments}\n editOpen={state.editOpen}\n setEditOpen={state.setEditOpen}\n onRefresh={() => state.detailQuery.refetch()}\n />\n\n <MessageDetailDialogs\n pendingActionConfirmation={state.pendingActionConfirmation}\n setPendingActionConfirmation={state.setPendingActionConfirmation}\n executingActionId={state.executingActionId}\n handleConfirmPendingAction={state.handleConfirmPendingAction}\n handleActionConfirmDialogKeyDown={state.handleActionConfirmDialogKeyDown}\n deleteConfirmationOpen={state.deleteConfirmationOpen}\n setDeleteConfirmationOpen={state.setDeleteConfirmationOpen}\n updatingState={state.updatingState}\n handleDelete={state.handleDelete}\n handleDeleteDialogKeyDown={state.handleDeleteDialogKeyDown}\n />\n </section>\n )\n}\n\nexport function MessageDetailPageClient({ id }: { id: string }) {\n const state = useMessageDetails(id)\n const [activeInlineComposer, setActiveInlineComposer] = React.useState<{\n variant: 'reply' | 'forward'\n messageId: string\n } | null>(null)\n const inlineComposerContainerRef = React.useRef<HTMLDivElement | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!activeInlineComposer) return\n\n const composerContainer = inlineComposerContainerRef.current\n if (!composerContainer) return\n\n const firstFocusableElement = composerContainer.querySelector<HTMLElement>(\n 'textarea, input, [contenteditable=\"true\"]',\n )\n firstFocusableElement?.focus()\n }, [activeInlineComposer])\n\n if (state.isLoadingDetail) {\n return <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n }\n\n if (!state.detail) {\n return (\n <ErrorMessage\n label={state.loadErrorMessage}\n action={(\n <Button type=\"button\" variant=\"outline\" onClick={state.backToList}>\n {state.t('messages.actions.backToList', 'Back to messages')}\n </Button>\n )}\n />\n )\n }\n\n const detail = state.detail\n const firstConversationMessageId = state.conversationItems[0]?.id ?? null\n const latestConversationMessageId = state.conversationItems[state.conversationItems.length - 1]?.id ?? null\n const canRunConversationActions = Boolean(firstConversationMessageId)\n\n return (\n <div className=\"space-y-3\">\n <MainMessageHeader\n subject={detail.subject}\n priority={(detail.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal'}\n canReply={!detail.isDraft && detail.typeDefinition.allowReply && Boolean(firstConversationMessageId)}\n canForwardAll={!detail.isDraft && detail.typeDefinition.allowForward && Boolean(latestConversationMessageId)}\n actionsDisabled={Boolean(state.activeConversationAction)}\n activeActionId={state.activeConversationAction}\n onReply={() => {\n if (!firstConversationMessageId) return\n setActiveInlineComposer({\n variant: 'reply',\n messageId: firstConversationMessageId,\n })\n }}\n onForwardAll={() => {\n if (!latestConversationMessageId) return\n setActiveInlineComposer({\n variant: 'forward',\n messageId: latestConversationMessageId,\n })\n }}\n onArchiveConversation={() => {\n if (!canRunConversationActions) return\n void state.archiveConversation(firstConversationMessageId ?? undefined)\n }}\n onMarkAllUnread={() => {\n if (!canRunConversationActions) return\n void state.markConversationUnread(firstConversationMessageId ?? undefined)\n }}\n onDeleteConversation={() => {\n if (!canRunConversationActions || state.activeConversationAction) return\n void (async () => {\n const confirmed = await confirm({\n title: state.t('messages.confirm.deleteConversationTitle', 'Delete conversation'),\n text: state.t('messages.confirm.deleteConversation', 'Delete this conversation from your view?'),\n confirmText: state.t('messages.actions.deleteConversation', 'Delete conversation'),\n cancelText: state.t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n await state.deleteConversation(firstConversationMessageId ?? undefined)\n })()\n }}\n />\n <div className=\"divide-y border-y\">\n {state.conversationItems.map((item) => {\n const isForcedExpanded = item.id === state.forcedExpandedItemId\n const isExpanded = state.isConversationItemExpanded(item.id)\n if (isExpanded) {\n return (\n <MessageConversationDetailItem\n key={item.id}\n messageId={item.id}\n isCollapsible={!isForcedExpanded}\n isExpanded\n onToggle={() => state.toggleConversationItem(item.id)}\n onReply={(messageId) => {\n setActiveInlineComposer({\n variant: 'reply',\n messageId,\n })\n }}\n onForward={(messageId) => {\n setActiveInlineComposer({\n variant: 'forward',\n messageId,\n })\n }}\n />\n )\n }\n\n return (\n <section key={item.id} className=\"px-1 py-1\">\n <MessageListComponent\n message={state.buildConversationListItemMessage(item)}\n onClick={() => state.toggleConversationItem(item.id)}\n />\n </section>\n )\n })}\n </div>\n\n {activeInlineComposer ? (\n <div ref={inlineComposerContainerRef}>\n <MessageComposer\n inline\n inlineBackHref={null}\n variant={activeInlineComposer.variant}\n messageId={activeInlineComposer.messageId}\n onCancel={() => {\n setActiveInlineComposer(null)\n }}\n onSuccess={() => {\n setActiveInlineComposer(null)\n void state.detailQuery.refetch()\n }}\n />\n </div>\n ) : null}\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { MessageComposer } from '@open-mercato/ui/backend/messages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getMessageUiComponentRegistry,\n} from './utils/typeUiRegistry'\nimport {\n MessageDetailActionsSection,\n MessageDetailAttachmentsSection,\n MessageDetailBodySection,\n MessageDetailComposerDialogs,\n MessageDetailDialogs,\n MainMessageHeader,\n MessageListComponent,\n MessageHeader,\n MessageDetailMetaSection,\n MessageDetailObjectsSection,\n} from './message-detail/detail-panels'\nimport { useMessageDetails } from './message-detail/hooks/useMessageDetails'\n\nfunction MessageConversationDetailItem({\n messageId,\n isCollapsible,\n isExpanded,\n onToggle,\n onReply,\n onForward,\n}: {\n messageId: string\n isCollapsible: boolean\n isExpanded: boolean\n onToggle: () => void\n onReply: (messageId: string) => void\n onForward: (messageId: string) => void\n}) {\n const state = useMessageDetails(messageId)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n\n if (state.isLoadingDetail) {\n return (\n <section className=\"py-3\">\n <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n </section>\n )\n }\n\n if (!state.detail) {\n return (\n <section className=\"py-3\">\n <ErrorMessage label={state.loadErrorMessage} />\n </section>\n )\n }\n\n const ContentComponent = state.contentComponentKey\n ? messageUiRegistry.contentComponents[state.contentComponentKey] ?? null\n : null\n const ActionsComponent = state.actionsComponentKey\n ? messageUiRegistry.actionsComponents[state.actionsComponentKey] ?? null\n : null\n\n return (\n <section className=\"py-3\">\n <div className=\"space-y-4\">\n <section className=\"space-y-2 py-2\">\n <MessageHeader\n detail={state.detail}\n updatingState={state.updatingState}\n isArchived={state.isArchived}\n showSubject={false}\n collapseToggle={isCollapsible ? { expanded: isExpanded, onToggle } : undefined}\n onReply={() => onReply(messageId)}\n onForward={() => onForward(messageId)}\n onEdit={() => state.setEditOpen(true)}\n onToggleRead={() => void state.toggleRead()}\n onToggleArchive={() => void state.toggleArchive()}\n onDelete={() => state.setDeleteConfirmationOpen(true)}\n />\n\n <MessageDetailBodySection\n detail={state.detail}\n contentProps={state.contentProps}\n ContentComponent={ContentComponent}\n />\n\n <MessageDetailMetaSection detail={state.detail} />\n </section>\n\n <MessageDetailActionsSection\n detail={state.detail}\n messageActions={state.messageActions}\n executingActionId={state.executingActionId}\n ActionsComponent={ActionsComponent}\n onExecuteActionById={state.handleExecuteActionById}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailObjectsSection\n detail={state.detail}\n objectActionsByObjectId={state.objectActionsByObjectId}\n onExecuteAction={state.handleExecuteAction}\n />\n\n <MessageDetailAttachmentsSection\n attachmentsQuery={state.attachmentsQuery}\n attachments={state.attachments}\n />\n </div>\n\n <MessageDetailComposerDialogs\n id={messageId}\n detail={state.detail}\n attachments={state.attachments}\n editOpen={state.editOpen}\n setEditOpen={state.setEditOpen}\n onRefresh={() => state.detailQuery.refetch()}\n />\n\n <MessageDetailDialogs\n pendingActionConfirmation={state.pendingActionConfirmation}\n setPendingActionConfirmation={state.setPendingActionConfirmation}\n executingActionId={state.executingActionId}\n handleConfirmPendingAction={state.handleConfirmPendingAction}\n handleActionConfirmDialogKeyDown={state.handleActionConfirmDialogKeyDown}\n deleteConfirmationOpen={state.deleteConfirmationOpen}\n setDeleteConfirmationOpen={state.setDeleteConfirmationOpen}\n updatingState={state.updatingState}\n handleDelete={state.handleDelete}\n handleDeleteDialogKeyDown={state.handleDeleteDialogKeyDown}\n />\n </section>\n )\n}\n\nfunction MessageDetailPageClientContent({ id }: { id: string }) {\n const state = useMessageDetails(id)\n const [activeInlineComposer, setActiveInlineComposer] = React.useState<{\n variant: 'reply' | 'forward'\n messageId: string\n } | null>(null)\n const inlineComposerContainerRef = React.useRef<HTMLDivElement | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!activeInlineComposer) return\n\n const composerContainer = inlineComposerContainerRef.current\n if (!composerContainer) return\n\n const firstFocusableElement = composerContainer.querySelector<HTMLElement>(\n 'textarea, input, [contenteditable=\"true\"]',\n )\n firstFocusableElement?.focus()\n }, [activeInlineComposer])\n\n if (state.isLoadingDetail) {\n return <LoadingMessage label={state.t('messages.loading.detail', 'Loading message...')} />\n }\n\n if (!state.detail) {\n return (\n <ErrorMessage\n label={state.loadErrorMessage}\n action={(\n <Button type=\"button\" variant=\"outline\" onClick={state.backToList}>\n {state.t('messages.actions.backToList', 'Back to messages')}\n </Button>\n )}\n />\n )\n }\n\n const detail = state.detail\n const firstConversationMessageId = state.conversationItems[0]?.id ?? null\n const latestConversationMessageId = state.conversationItems[state.conversationItems.length - 1]?.id ?? null\n const canRunConversationActions = Boolean(firstConversationMessageId)\n\n return (\n <div className=\"space-y-3\">\n <MainMessageHeader\n subject={detail.subject}\n priority={(detail.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal'}\n canReply={!detail.isDraft && detail.typeDefinition.allowReply && Boolean(firstConversationMessageId)}\n canForwardAll={!detail.isDraft && detail.typeDefinition.allowForward && Boolean(latestConversationMessageId)}\n actionsDisabled={Boolean(state.activeConversationAction)}\n activeActionId={state.activeConversationAction}\n onReply={() => {\n if (!firstConversationMessageId) return\n setActiveInlineComposer({\n variant: 'reply',\n messageId: firstConversationMessageId,\n })\n }}\n onForwardAll={() => {\n if (!latestConversationMessageId) return\n setActiveInlineComposer({\n variant: 'forward',\n messageId: latestConversationMessageId,\n })\n }}\n onArchiveConversation={() => {\n if (!canRunConversationActions) return\n void state.archiveConversation(firstConversationMessageId ?? undefined)\n }}\n onMarkAllUnread={() => {\n if (!canRunConversationActions) return\n void state.markConversationUnread(firstConversationMessageId ?? undefined)\n }}\n onDeleteConversation={() => {\n if (!canRunConversationActions || state.activeConversationAction) return\n void (async () => {\n const confirmed = await confirm({\n title: state.t('messages.confirm.deleteConversationTitle', 'Delete conversation'),\n text: state.t('messages.confirm.deleteConversation', 'Delete this conversation from your view?'),\n confirmText: state.t('messages.actions.deleteConversation', 'Delete conversation'),\n cancelText: state.t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n await state.deleteConversation(firstConversationMessageId ?? undefined)\n })()\n }}\n />\n <div className=\"divide-y border-y\">\n {state.conversationItems.map((item) => {\n const isForcedExpanded = item.id === state.forcedExpandedItemId\n const isExpanded = state.isConversationItemExpanded(item.id)\n if (isExpanded) {\n return (\n <MessageConversationDetailItem\n key={item.id}\n messageId={item.id}\n isCollapsible={!isForcedExpanded}\n isExpanded\n onToggle={() => state.toggleConversationItem(item.id)}\n onReply={(messageId) => {\n setActiveInlineComposer({\n variant: 'reply',\n messageId,\n })\n }}\n onForward={(messageId) => {\n setActiveInlineComposer({\n variant: 'forward',\n messageId,\n })\n }}\n />\n )\n }\n\n return (\n <section key={item.id} className=\"px-1 py-1\">\n <MessageListComponent\n message={state.buildConversationListItemMessage(item)}\n onClick={() => state.toggleConversationItem(item.id)}\n />\n </section>\n )\n })}\n </div>\n\n {activeInlineComposer ? (\n <div ref={inlineComposerContainerRef}>\n <MessageComposer\n inline\n inlineBackHref={null}\n variant={activeInlineComposer.variant}\n messageId={activeInlineComposer.messageId}\n onCancel={() => {\n setActiveInlineComposer(null)\n }}\n onSuccess={() => {\n setActiveInlineComposer(null)\n void state.detailQuery.refetch()\n }}\n />\n </div>\n ) : null}\n {ConfirmDialogElement}\n </div>\n )\n}\n\nexport function MessageDetailPageClient({ id, canViewMessages = true }: { id: string; canViewMessages?: boolean }) {\n const t = useT()\n if (!canViewMessages) {\n return (\n <ErrorMessage\n label={t('messages.access.disabled.title', 'Messages module is disabled for your role.')}\n description={t(\n 'messages.access.disabled.description',\n 'Ask your administrator to enable the required Messages permissions.',\n )}\n />\n )\n }\n return <MessageDetailPageClientContent id={id} />\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,6 +7,7 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
7
7
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
8
8
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
9
9
|
import { DataTable } from "@open-mercato/ui/backend/DataTable";
|
|
10
|
+
import { ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
10
11
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
11
12
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
12
13
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
@@ -29,7 +30,7 @@ function toErrorMessage(payload) {
|
|
|
29
30
|
}
|
|
30
31
|
return null;
|
|
31
32
|
}
|
|
32
|
-
function MessagesInboxPageClient() {
|
|
33
|
+
function MessagesInboxPageClient({ canViewMessages = true }) {
|
|
33
34
|
const router = useRouter();
|
|
34
35
|
const t = useT();
|
|
35
36
|
const scopeVersion = useOrganizationScopeVersion();
|
|
@@ -52,6 +53,7 @@ function MessagesInboxPageClient() {
|
|
|
52
53
|
JSON.stringify(filterValues),
|
|
53
54
|
scopeVersion
|
|
54
55
|
],
|
|
56
|
+
enabled: canViewMessages,
|
|
55
57
|
queryFn: async () => {
|
|
56
58
|
const params = new URLSearchParams();
|
|
57
59
|
params.set("folder", folder);
|
|
@@ -76,6 +78,16 @@ function MessagesInboxPageClient() {
|
|
|
76
78
|
if (since) params.set("since", since);
|
|
77
79
|
const call = await apiCall(`/api/messages?${params.toString()}`);
|
|
78
80
|
if (!call.ok) {
|
|
81
|
+
if (call.status === 403) {
|
|
82
|
+
return {
|
|
83
|
+
items: [],
|
|
84
|
+
total: 0,
|
|
85
|
+
page,
|
|
86
|
+
pageSize,
|
|
87
|
+
totalPages: 1,
|
|
88
|
+
accessDenied: true
|
|
89
|
+
};
|
|
90
|
+
}
|
|
79
91
|
throw new Error(
|
|
80
92
|
toErrorMessage(call.result) ?? t("messages.errors.loadListFailed", "Failed to load messages.")
|
|
81
93
|
);
|
|
@@ -85,15 +97,18 @@ function MessagesInboxPageClient() {
|
|
|
85
97
|
total: Number(call.result?.total ?? 0),
|
|
86
98
|
page: Number(call.result?.page ?? page),
|
|
87
99
|
pageSize: Number(call.result?.pageSize ?? pageSize),
|
|
88
|
-
totalPages: Number(call.result?.totalPages ?? 1)
|
|
100
|
+
totalPages: Number(call.result?.totalPages ?? 1),
|
|
101
|
+
accessDenied: false
|
|
89
102
|
};
|
|
90
103
|
}
|
|
91
104
|
});
|
|
92
105
|
const messageTypesQuery = useQuery({
|
|
93
106
|
queryKey: ["messages", "types", scopeVersion],
|
|
107
|
+
enabled: canViewMessages,
|
|
94
108
|
queryFn: async () => {
|
|
95
109
|
const call = await apiCall("/api/messages/types");
|
|
96
110
|
if (!call.ok) {
|
|
111
|
+
if (call.status === 403) return [];
|
|
97
112
|
throw new Error(
|
|
98
113
|
toErrorMessage(call.result) ?? t("messages.errors.loadTypesFailed", "Failed to load message types.")
|
|
99
114
|
);
|
|
@@ -102,19 +117,22 @@ function MessagesInboxPageClient() {
|
|
|
102
117
|
}
|
|
103
118
|
});
|
|
104
119
|
React.useEffect(() => {
|
|
120
|
+
if (!canViewMessages) return;
|
|
105
121
|
if (!listQuery.error) return;
|
|
122
|
+
if (listQuery.data?.accessDenied) return;
|
|
106
123
|
flash(
|
|
107
124
|
listQuery.error instanceof Error ? listQuery.error.message : t("messages.errors.loadListFailed", "Failed to load messages."),
|
|
108
125
|
"error"
|
|
109
126
|
);
|
|
110
|
-
}, [listQuery.error, t]);
|
|
127
|
+
}, [canViewMessages, listQuery.error, t]);
|
|
111
128
|
React.useEffect(() => {
|
|
129
|
+
if (!canViewMessages) return;
|
|
112
130
|
if (!messageTypesQuery.error) return;
|
|
113
131
|
flash(
|
|
114
132
|
messageTypesQuery.error instanceof Error ? messageTypesQuery.error.message : t("messages.errors.loadTypesFailed", "Failed to load message types."),
|
|
115
133
|
"error"
|
|
116
134
|
);
|
|
117
|
-
}, [messageTypesQuery.error, t]);
|
|
135
|
+
}, [canViewMessages, messageTypesQuery.error, t]);
|
|
118
136
|
const messageTypeLabelMap = React.useMemo(() => {
|
|
119
137
|
const map = {};
|
|
120
138
|
for (const item of messageTypesQuery.data ?? []) {
|
|
@@ -287,9 +305,22 @@ function MessagesInboxPageClient() {
|
|
|
287
305
|
document.removeEventListener("keydown", handleEscape);
|
|
288
306
|
};
|
|
289
307
|
}, [folderMenuOpen]);
|
|
308
|
+
const accessDenied = !canViewMessages || listQuery.data?.accessDenied === true;
|
|
290
309
|
const rows = listQuery.data?.items ?? [];
|
|
291
310
|
const total = listQuery.data?.total ?? 0;
|
|
292
311
|
const totalPages = listQuery.data?.totalPages ?? 1;
|
|
312
|
+
if (accessDenied) {
|
|
313
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
314
|
+
ErrorMessage,
|
|
315
|
+
{
|
|
316
|
+
label: t("messages.access.disabled.title", "Messages module is disabled for your role."),
|
|
317
|
+
description: t(
|
|
318
|
+
"messages.access.disabled.description",
|
|
319
|
+
"Ask your administrator to enable the required Messages permissions."
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
) });
|
|
323
|
+
}
|
|
293
324
|
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
294
325
|
DataTable,
|
|
295
326
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/MessagesInboxPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\n\ntype MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nfunction toErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const item of payload) {\n const nested = toErrorMessage(item)\n if (nested) return nested\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n return (\n toErrorMessage(record.error)\n ?? toErrorMessage(record.message)\n ?? toErrorMessage(record.detail)\n ?? toErrorMessage(record.details)\n ?? null\n )\n }\n return null\n}\n\nexport function MessagesInboxPageClient() {\n const router = useRouter()\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n queryFn: async () => {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const status = typeof filterValues.status === 'string' ? filterValues.status.trim() : ''\n const type = typeof filterValues.type === 'string' ? filterValues.type.trim() : ''\n const hasObjects = typeof filterValues.hasObjects === 'string' ? filterValues.hasObjects.trim() : ''\n const hasAttachments = typeof filterValues.hasAttachments === 'string' ? filterValues.hasAttachments.trim() : ''\n const hasActions = typeof filterValues.hasActions === 'string' ? filterValues.hasActions.trim() : ''\n const senderId = typeof filterValues.senderId === 'string' ? filterValues.senderId.trim() : ''\n const since = typeof filterValues.since === 'string' ? filterValues.since.trim() : ''\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (since) params.set('since', since)\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 1),\n }\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!listQuery.error) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [listQuery.error, t])\n\n React.useEffect(() => {\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(`/api/auth/users?${params.toString()}`)\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n return items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n }, [])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Objects'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Actions'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }],\n loadOptions: loadSenderOptions,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DDTHH:mm:ssZ'),\n },\n ]\n }, [loadSenderOptions, messageTypesQuery.data, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: item.senderName || item.senderEmail || item.senderUserId,\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 1\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-20 mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <button\n key={option.id}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n perspective={{ tableId: 'messages.inbox' }}\n embedded\n />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\n\ntype MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageListQueryResult = {\n items: MessageListItem[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n accessDenied: boolean\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nfunction toErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const item of payload) {\n const nested = toErrorMessage(item)\n if (nested) return nested\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n return (\n toErrorMessage(record.error)\n ?? toErrorMessage(record.message)\n ?? toErrorMessage(record.detail)\n ?? toErrorMessage(record.details)\n ?? null\n )\n }\n return null\n}\n\nexport function MessagesInboxPageClient({ canViewMessages = true }: { canViewMessages?: boolean }) {\n const router = useRouter()\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n enabled: canViewMessages,\n queryFn: async () => {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const status = typeof filterValues.status === 'string' ? filterValues.status.trim() : ''\n const type = typeof filterValues.type === 'string' ? filterValues.type.trim() : ''\n const hasObjects = typeof filterValues.hasObjects === 'string' ? filterValues.hasObjects.trim() : ''\n const hasAttachments = typeof filterValues.hasAttachments === 'string' ? filterValues.hasAttachments.trim() : ''\n const hasActions = typeof filterValues.hasActions === 'string' ? filterValues.hasActions.trim() : ''\n const senderId = typeof filterValues.senderId === 'string' ? filterValues.senderId.trim() : ''\n const since = typeof filterValues.since === 'string' ? filterValues.since.trim() : ''\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (since) params.set('since', since)\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n if (call.status === 403) {\n return {\n items: [],\n total: 0,\n page,\n pageSize,\n totalPages: 1,\n accessDenied: true,\n } satisfies MessageListQueryResult\n }\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 1),\n accessDenied: false,\n } satisfies MessageListQueryResult\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n enabled: canViewMessages,\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n if (call.status === 403) return []\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!canViewMessages) return\n if (!listQuery.error) return\n if (listQuery.data?.accessDenied) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [canViewMessages, listQuery.error, t])\n\n React.useEffect(() => {\n if (!canViewMessages) return\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [canViewMessages, messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(`/api/auth/users?${params.toString()}`)\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n return items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n }, [])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Objects'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Actions'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }],\n loadOptions: loadSenderOptions,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DDTHH:mm:ssZ'),\n },\n ]\n }, [loadSenderOptions, messageTypesQuery.data, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: item.senderName || item.senderEmail || item.senderUserId,\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const accessDenied = !canViewMessages || listQuery.data?.accessDenied === true\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 1\n\n if (accessDenied) {\n return (\n <div className=\"space-y-4\">\n <ErrorMessage\n label={t('messages.access.disabled.title', 'Messages module is disabled for your role.')}\n description={t(\n 'messages.access.disabled.description',\n 'Ask your administrator to enable the required Messages permissions.',\n )}\n />\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-20 mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <button\n key={option.id}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n perspective={{ tableId: 'messages.inbox' }}\n embedded\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAyVU,cAuHM,YAvHN;AAvVV,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AAEzB,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAC5C,SAAS,iBAAiB;AAE1B,SAAS,oBAAoB;AAC7B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,SAAS,aAAa,aAAa,OAAO,QAAQ,YAAY;AACvE,SAAS,qCAAqC;AAC9C,SAAS,8BAA8B;AAyDvC,SAAS,eAAe,SAAiC;AACvD,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,eAAW,QAAQ,SAAS;AAC1B,YAAM,SAAS,eAAe,IAAI;AAClC,UAAI,OAAQ,QAAO;AAAA,IACrB;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,YAAY,UAAU;AAC/B,UAAM,SAAS;AACf,WACE,eAAe,OAAO,KAAK,KACxB,eAAe,OAAO,OAAO,KAC7B,eAAe,OAAO,MAAM,KAC5B,eAAe,OAAO,OAAO,KAC7B;AAAA,EAEP;AACA,SAAO;AACT;AAEO,SAAS,wBAAwB,EAAE,kBAAkB,KAAK,GAAkC;AACjG,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,4BAA4B;AAEjD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAwB,OAAO;AACjE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,KAAK;AAChE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAuB,CAAC,CAAC;AACvE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,WAAW;AACjB,QAAM,gBAAgB,MAAM,OAA8B,IAAI;AAC9D,QAAM,oBAAoB,MAAM,QAAQ,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAEjF,QAAM,YAAY,SAAS;AAAA,IACzB,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,UAAU,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,SAAS;AAAA,IACT,SAAS,YAAY;AACnB,YAAM,SAAS,IAAI,gBAAgB;AACnC,aAAO,IAAI,UAAU,MAAM;AAC3B,aAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,aAAO,IAAI,YAAY,OAAO,QAAQ,CAAC;AAEvC,UAAI,OAAO,KAAK,GAAG;AACjB,eAAO,IAAI,UAAU,OAAO,KAAK,CAAC;AAAA,MACpC;AAEA,YAAM,SAAS,OAAO,aAAa,WAAW,WAAW,aAAa,OAAO,KAAK,IAAI;AACtF,YAAM,OAAO,OAAO,aAAa,SAAS,WAAW,aAAa,KAAK,KAAK,IAAI;AAChF,YAAM,aAAa,OAAO,aAAa,eAAe,WAAW,aAAa,WAAW,KAAK,IAAI;AAClG,YAAM,iBAAiB,OAAO,aAAa,mBAAmB,WAAW,aAAa,eAAe,KAAK,IAAI;AAC9G,YAAM,aAAa,OAAO,aAAa,eAAe,WAAW,aAAa,WAAW,KAAK,IAAI;AAClG,YAAM,WAAW,OAAO,aAAa,aAAa,WAAW,aAAa,SAAS,KAAK,IAAI;AAC5F,YAAM,QAAQ,OAAO,aAAa,UAAU,WAAW,aAAa,MAAM,KAAK,IAAI;AAEnF,UAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AACvC,UAAI,KAAM,QAAO,IAAI,QAAQ,IAAI;AACjC,UAAI,WAAY,QAAO,IAAI,cAAc,UAAU;AACnD,UAAI,eAAgB,QAAO,IAAI,kBAAkB,cAAc;AAC/D,UAAI,WAAY,QAAO,IAAI,cAAc,UAAU;AACnD,UAAI,SAAU,QAAO,IAAI,YAAY,QAAQ;AAC7C,UAAI,MAAO,QAAO,IAAI,SAAS,KAAK;AAEpC,YAAM,OAAO,MAAM,QAA6B,iBAAiB,OAAO,SAAS,CAAC,EAAE;AACpF,UAAI,CAAC,KAAK,IAAI;AACZ,YAAI,KAAK,WAAW,KAAK;AACvB,iBAAO;AAAA,YACL,OAAO,CAAC;AAAA,YACR,OAAO;AAAA,YACP;AAAA,YACA;AAAA,YACA,YAAY;AAAA,YACZ,cAAc;AAAA,UAChB;AAAA,QACF;AACA,cAAM,IAAI;AAAA,UACR,eAAe,KAAK,MAAM,KACvB,EAAE,kCAAkC,0BAA0B;AAAA,QACnE;AAAA,MACF;AAEA,aAAO;AAAA,QACL,OAAO,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC,IAAI,CAAC;AAAA,QACvE,OAAO,OAAO,KAAK,QAAQ,SAAS,CAAC;AAAA,QACrC,MAAM,OAAO,KAAK,QAAQ,QAAQ,IAAI;AAAA,QACtC,UAAU,OAAO,KAAK,QAAQ,YAAY,QAAQ;AAAA,QAClD,YAAY,OAAO,KAAK,QAAQ,cAAc,CAAC;AAAA,QAC/C,cAAc;AAAA,MAChB;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,oBAAoB,SAAS;AAAA,IACjC,UAAU,CAAC,YAAY,SAAS,YAAY;AAAA,IAC5C,SAAS;AAAA,IACT,SAAS,YAAY;AACnB,YAAM,OAAO,MAAM,QAAuC,qBAAqB;AAC/E,UAAI,CAAC,KAAK,IAAI;AACZ,YAAI,KAAK,WAAW,IAAK,QAAO,CAAC;AACjC,cAAM,IAAI;AAAA,UACR,eAAe,KAAK,MAAM,KACvB,EAAE,mCAAmC,+BAA+B;AAAA,QACzE;AAAA,MACF;AACA,aAAO,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC,IAAI,CAAC;AAAA,IACzE;AAAA,EACF,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,gBAAiB;AACtB,QAAI,CAAC,UAAU,MAAO;AACtB,QAAI,UAAU,MAAM,aAAc;AAClC;AAAA,MACE,UAAU,iBAAiB,QACvB,UAAU,MAAM,UAChB,EAAE,kCAAkC,0BAA0B;AAAA,MAClE;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,UAAU,OAAO,CAAC,CAAC;AAExC,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,gBAAiB;AACtB,QAAI,CAAC,kBAAkB,MAAO;AAC9B;AAAA,MACE,kBAAkB,iBAAiB,QAC/B,kBAAkB,MAAM,UACxB,EAAE,mCAAmC,+BAA+B;AAAA,MACxE;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,kBAAkB,OAAO,CAAC,CAAC;AAEhD,QAAM,sBAAsB,MAAM,QAAQ,MAAM;AAC9C,UAAM,MAA8B,CAAC;AACrC,eAAW,QAAQ,kBAAkB,QAAQ,CAAC,GAAG;AAC/C,UAAI,KAAK,IAAI,IAAI,EAAE,KAAK,UAAU,KAAK,IAAI;AAAA,IAC7C;AACA,WAAO;AAAA,EACT,GAAG,CAAC,kBAAkB,MAAM,CAAC,CAAC;AAE9B,QAAM,oBAAoB,MAAM,YAAY,OAAO,UAAmB;AACpE,UAAM,SAAS,IAAI,gBAAgB;AACnC,WAAO,IAAI,QAAQ,GAAG;AACtB,WAAO,IAAI,YAAY,IAAI;AAC3B,QAAI,SAAS,MAAM,KAAK,EAAE,SAAS,GAAG;AACpC,aAAO,IAAI,UAAU,MAAM,KAAK,CAAC;AAAA,IACnC;AAEA,UAAM,OAAO,MAAM,QAAoC,mBAAmB,OAAO,SAAS,CAAC,EAAE;AAC7F,QAAI,CAAC,KAAK,GAAI,QAAO,CAAC;AAEtB,UAAM,QAAQ,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC,IAAI,CAAC;AAC9E,WAAO,MAAM,QAAQ,CAAC,SAAS;AAC7B,UAAI,CAAC,QAAQ,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,KAAK,EAAE,WAAW,EAAG,QAAO,CAAC;AACjF,YAAM,OAAO,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,KAAK,EAAE,SAAS,IAAI,KAAK,KAAK,KAAK,IAAI;AAC/F,YAAM,QAAQ,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,SAAS,IAAI,KAAK,MAAM,KAAK,IAAI;AACnG,YAAM,QAAQ,QAAQ,SAAS,KAAK;AACpC,aAAO,CAAC;AAAA,QACN,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,aAAa,SAAS,UAAU,QAAQ,QAAQ;AAAA,MAClD,CAAC;AAAA,IACH,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,6BAA6B,MAAM,QAAQ,MAAM;AACrD,UAAM,MAAqC,CAAC;AAC5C,eAAW,QAAQ,kBAAkB,QAAQ,CAAC,GAAG;AAC/C,UAAI,KAAK,IAAI,IAAI,KAAK,IAAI,qBAAqB;AAAA,IACjD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,kBAAkB,IAAI,CAAC;AAE3B,QAAM,UAAU,MAAM,QAAqB,MAAM;AAC/C,UAAM,eAAe,kBAAkB,QAAQ,CAAC,GAAG,IAAI,CAAC,UAAU;AAAA,MAChE,OAAO,KAAK;AAAA,MACZ,OAAO,EAAE,KAAK,UAAU,KAAK,IAAI;AAAA,IACnC,EAAE;AAEF,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,2BAA2B,QAAQ;AAAA,QAC5C,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,UAAU,OAAO,EAAE,0BAA0B,QAAQ,EAAE;AAAA,UAChE,EAAE,OAAO,QAAQ,OAAO,EAAE,wBAAwB,MAAM,EAAE;AAAA,UAC1D,EAAE,OAAO,YAAY,OAAO,EAAE,4BAA4B,UAAU,EAAE;AAAA,QACxE;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,yBAAyB,MAAM;AAAA,QACxC,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE,GAAG,GAAG,WAAW;AAAA,MAClF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,+BAA+B,SAAS;AAAA,QACjD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,QAAQ,OAAO,EAAE,cAAc,KAAK,EAAE;AAAA,UAC/C,EAAE,OAAO,SAAS,OAAO,EAAE,aAAa,IAAI,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,mCAAmC,aAAa;AAAA,QACzD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,QAAQ,OAAO,EAAE,cAAc,KAAK,EAAE;AAAA,UAC/C,EAAE,OAAO,SAAS,OAAO,EAAE,aAAa,IAAI,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,+BAA+B,SAAS;AAAA,QACjD,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,UACrD,EAAE,OAAO,QAAQ,OAAO,EAAE,cAAc,KAAK,EAAE;AAAA,UAC/C,EAAE,OAAO,SAAS,OAAO,EAAE,aAAa,IAAI,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,2BAA2B,QAAQ;AAAA,QAC5C,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE,CAAC;AAAA,QAChE,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,0BAA0B,YAAY;AAAA,QAC/C,MAAM;AAAA,QACN,aAAa,EAAE,qCAAqC,sBAAsB;AAAA,MAC5E;AAAA,IACF;AAAA,EACF,GAAG,CAAC,mBAAmB,kBAAkB,MAAM,CAAC,CAAC;AAEjD,QAAM,UAAU,MAAM,QAAsC,MAAM;AAAA,IAChE;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,EAAE,kBAAkB,UAAU;AAAA,MACtC,MAAM;AAAA,QACJ,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,MACA,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,cAAM,OAAO,IAAI;AACjB,cAAM,uBAAuB,2BAA2B,KAAK,IAAI;AACjE,cAAM,oBAAoB,uBACtB,kBAAkB,mBAAmB,oBAAoB,KAAK,OAC9D;AACJ,cAAM,iBAAiB,qBAAqB;AAE5C,eACE;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,cACP,IAAI,KAAK;AAAA,cACT,MAAM,KAAK;AAAA,cACX,WAAW,oBAAoB,KAAK,IAAI,KAAK,KAAK;AAAA,cAClD,SAAS,KAAK;AAAA,cACd,MAAM,KAAK;AAAA,cACX,YAAY;AAAA,cACZ,UAAW,KAAK,YAAqD;AAAA,cACrE,QAAQ,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,IAAI;AAAA,cAC9C,YAAY,KAAK,cAAc,KAAK,eAAe,KAAK;AAAA,cACxD,YAAY,KAAK;AAAA,cACjB,aAAa,KAAK;AAAA,cAClB,gBAAgB,KAAK;AAAA,cACrB,iBAAiB,KAAK;AAAA,cACtB,YAAY,KAAK;AAAA,cACjB,aAAa,KAAK,eAAe;AAAA,cACjC,QAAQ,KAAK,WAAW;AAAA,YAC1B;AAAA,YACA,SAAS,MAAM,OAAO,KAAK,qBAAqB,KAAK,EAAE,EAAE;AAAA;AAAA,QAC3D;AAAA,MAEJ;AAAA,IACF;AAAA,EACF,GAAG,CAAC,4BAA4B,qBAAqB,mBAAmB,QAAQ,CAAC,CAAC;AAElF,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AAAA,IACxC,EAAE,IAAI,SAAkB,OAAO,EAAE,yBAAyB,OAAO,GAAG,MAAM,MAAM;AAAA,IAChF,EAAE,IAAI,QAAiB,OAAO,EAAE,wBAAwB,MAAM,GAAG,MAAM,KAAK;AAAA,IAC5E,EAAE,IAAI,UAAmB,OAAO,EAAE,0BAA0B,QAAQ,GAAG,MAAM,YAAY;AAAA,IACzF,EAAE,IAAI,YAAqB,OAAO,EAAE,4BAA4B,UAAU,GAAG,MAAM,QAAQ;AAAA,IAC3F,EAAE,IAAI,OAAgB,OAAO,EAAE,uBAAuB,KAAK,GAAG,MAAM,OAAO;AAAA,EAC7E,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,qBAAqB,cAAc,KAAK,CAAC,WAAW,OAAO,OAAO,MAAM,KAAK,cAAc,CAAC;AAClG,QAAM,mBAAmB,mBAAmB;AAE5C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,eAAgB;AACrB,UAAM,qBAAqB,CAAC,UAAsB;AAChD,UAAI,CAAC,cAAc,QAAS;AAC5B,YAAM,SAAS,MAAM;AACrB,UAAI,kBAAkB,QAAQ,CAAC,cAAc,QAAQ,SAAS,MAAM,GAAG;AACrE,0BAAkB,KAAK;AAAA,MACzB;AAAA,IACF;AACA,UAAM,eAAe,CAAC,UAAyB;AAC7C,UAAI,MAAM,QAAQ,SAAU,mBAAkB,KAAK;AAAA,IACrD;AACA,aAAS,iBAAiB,aAAa,kBAAkB;AACzD,aAAS,iBAAiB,WAAW,YAAY;AACjD,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,kBAAkB;AAC5D,eAAS,oBAAoB,WAAW,YAAY;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,eAAe,CAAC,mBAAmB,UAAU,MAAM,iBAAiB;AAC1E,QAAM,OAAO,UAAU,MAAM,SAAS,CAAC;AACvC,QAAM,QAAQ,UAAU,MAAM,SAAS;AACvC,QAAM,aAAa,UAAU,MAAM,cAAc;AAEjD,MAAI,cAAc;AAChB,WACE,oBAAC,SAAI,WAAU,aACb;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,kCAAkC,4CAA4C;AAAA,QACvF,aAAa;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA;AAAA,IACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAU,aACb;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,kBAAkB,UAAU;AAAA,MACrC;AAAA,MACA,MAAM;AAAA,MACN,aAAa;AAAA,MACb,gBAAgB,CAAC,UAAU;AACzB,kBAAU,KAAK;AACf,gBAAQ,CAAC;AAAA,MACX;AAAA,MACA,mBAAmB,EAAE,8BAA8B,iBAAiB;AAAA,MACpE;AAAA,MACA;AAAA,MACA,gBAAgB,CAAC,UAAU;AACzB,wBAAgB,KAAK;AACrB,gBAAQ,CAAC;AAAA,MACX;AAAA,MACA,gBAAgB,MAAM;AACpB,wBAAgB,CAAC,CAAC;AAClB,gBAAQ,CAAC;AAAA,MACX;AAAA,MACA,WAAW,UAAU,aAAa,UAAU;AAAA,MAC5C,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc;AAAA,MAChB;AAAA,MACA,SACE,qBAAC,SAAI,WAAU,qCACb;AAAA,6BAAC,SAAI,WAAU,YAAW,KAAK,eAC7B;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,iBAAe;AAAA,cACf,iBAAc;AAAA,cACd,SAAS,MAAM,kBAAkB,CAAC,UAAU,CAAC,KAAK;AAAA,cAElD;AAAA,oCAAC,oBAAiB,WAAU,WAAU,eAAW,MAAC;AAAA,gBAClD,qBAAC,UAAM;AAAA,oBAAE,4BAA4B,QAAQ;AAAA,kBAAE;AAAA,mBAAC;AAAA,gBAChD,oBAAC,UAAM,6BAAmB,OAAM;AAAA,gBAChC,oBAAC,eAAY,WAAU,sBAAqB,eAAW,MAAC;AAAA;AAAA;AAAA,UAC1D;AAAA,UACC,iBACC;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,MAAK;AAAA,cAEJ,wBAAc,IAAI,CAAC,WAAW;AAC7B,sBAAM,OAAO,OAAO;AACpB,sBAAM,WAAW,OAAO,OAAO;AAC/B,uBACE;AAAA,kBAAC;AAAA;AAAA,oBAEC,MAAK;AAAA,oBACL,MAAK;AAAA,oBACL,gBAAc;AAAA,oBACd,WAAW,wFAAwF,WAAW,iBAAiB,EAAE;AAAA,oBACjI,SAAS,MAAM;AACb,gCAAU,OAAO,EAAE;AACnB,8BAAQ,CAAC;AACT,wCAAkB,KAAK;AAAA,oBACzB;AAAA,oBAEA;AAAA,0CAAC,QAAK,WAAU,WAAU,eAAW,MAAC;AAAA,sBACtC,oBAAC,UAAM,iBAAO,OAAM;AAAA;AAAA;AAAA,kBAZf,OAAO;AAAA,gBAad;AAAA,cAEJ,CAAC;AAAA;AAAA,UACH,IACE;AAAA,WACN;AAAA,QACA,oBAAC,UAAO,SAAO,MACb,8BAAC,QAAK,MAAK,6BAA6B,YAAE,oBAAoB,iBAAiB,GAAE,GACnF;AAAA,SACF;AAAA,MAEF,YAAY,CAAC,QAAQ;AACnB,eAAO,KAAK,qBAAqB,IAAI,EAAE,EAAE;AAAA,MAC3C;AAAA,MACA,aAAa,EAAE,SAAS,iBAAiB;AAAA,MACzC,UAAQ;AAAA;AAAA,EACV,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cookies } from "next/headers";
|
|
2
|
+
import { getAuthFromCookies } from "@open-mercato/shared/lib/auth/server";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { resolveFeatureCheckContext } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
5
|
+
async function resolveCanViewMessagesForCurrentUser() {
|
|
6
|
+
const auth = await getAuthFromCookies();
|
|
7
|
+
if (!auth) return false;
|
|
8
|
+
try {
|
|
9
|
+
const cookieStore = await cookies();
|
|
10
|
+
const rawSelectedOrg = cookieStore.get("om_selected_org")?.value;
|
|
11
|
+
const rawSelectedTenant = cookieStore.get("om_selected_tenant")?.value;
|
|
12
|
+
const selectedOrgForScope = rawSelectedOrg === void 0 ? void 0 : rawSelectedOrg && rawSelectedOrg.trim().length > 0 ? rawSelectedOrg : null;
|
|
13
|
+
const selectedTenantForScope = rawSelectedTenant === void 0 ? void 0 : rawSelectedTenant && rawSelectedTenant.trim().length > 0 ? rawSelectedTenant : null;
|
|
14
|
+
const container = await createRequestContainer();
|
|
15
|
+
const rbac = container.resolve("rbacService");
|
|
16
|
+
const { organizationId, scope, allowedOrganizationIds } = await resolveFeatureCheckContext({
|
|
17
|
+
container,
|
|
18
|
+
auth,
|
|
19
|
+
selectedId: selectedOrgForScope,
|
|
20
|
+
tenantId: selectedTenantForScope
|
|
21
|
+
});
|
|
22
|
+
if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return await rbac.userHasAllFeatures(auth.sub, ["messages.view"], {
|
|
26
|
+
tenantId: scope.tenantId ?? auth.tenantId ?? null,
|
|
27
|
+
organizationId: organizationId ?? null
|
|
28
|
+
});
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
resolveCanViewMessagesForCurrentUser
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/messages/lib/access.ts"],
|
|
4
|
+
"sourcesContent": ["import { cookies } from 'next/headers'\nimport { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nexport async function resolveCanViewMessagesForCurrentUser(): Promise<boolean> {\n const auth = await getAuthFromCookies()\n if (!auth) return false\n\n try {\n const cookieStore = await cookies()\n const rawSelectedOrg = cookieStore.get('om_selected_org')?.value\n const rawSelectedTenant = cookieStore.get('om_selected_tenant')?.value\n const selectedOrgForScope = rawSelectedOrg === undefined\n ? undefined\n : rawSelectedOrg && rawSelectedOrg.trim().length > 0\n ? rawSelectedOrg\n : null\n const selectedTenantForScope = rawSelectedTenant === undefined\n ? undefined\n : rawSelectedTenant && rawSelectedTenant.trim().length > 0\n ? rawSelectedTenant\n : null\n\n const container = await createRequestContainer()\n const rbac = container.resolve<RbacService>('rbacService')\n const { organizationId, scope, allowedOrganizationIds } = await resolveFeatureCheckContext({\n container,\n auth,\n selectedId: selectedOrgForScope,\n tenantId: selectedTenantForScope,\n })\n\n if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {\n return false\n }\n\n return await rbac.userHasAllFeatures(auth.sub, ['messages.view'], {\n tenantId: scope.tenantId ?? auth.tenantId ?? null,\n organizationId: organizationId ?? null,\n })\n } catch {\n return false\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,eAAe;AACxB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,kCAAkC;AAG3C,eAAsB,uCAAyD;AAC7E,QAAM,OAAO,MAAM,mBAAmB;AACtC,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI;AACF,UAAM,cAAc,MAAM,QAAQ;AAClC,UAAM,iBAAiB,YAAY,IAAI,iBAAiB,GAAG;AAC3D,UAAM,oBAAoB,YAAY,IAAI,oBAAoB,GAAG;AACjE,UAAM,sBAAsB,mBAAmB,SAC3C,SACA,kBAAkB,eAAe,KAAK,EAAE,SAAS,IAC/C,iBACA;AACN,UAAM,yBAAyB,sBAAsB,SACjD,SACA,qBAAqB,kBAAkB,KAAK,EAAE,SAAS,IACrD,oBACA;AAEN,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,UAAU,QAAqB,aAAa;AACzD,UAAM,EAAE,gBAAgB,OAAO,uBAAuB,IAAI,MAAM,2BAA2B;AAAA,MACzF;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ,UAAU;AAAA,IACZ,CAAC;AAED,QAAI,MAAM,QAAQ,sBAAsB,KAAK,uBAAuB,WAAW,GAAG;AAChF,aAAO;AAAA,IACT;AAEA,WAAO,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,eAAe,GAAG;AAAA,MAChE,UAAU,MAAM,YAAY,KAAK,YAAY;AAAA,MAC7C,gBAAgB,kBAAkB;AAAA,IACpC,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6-canary-c15f8174eb",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.
|
|
210
|
+
"@open-mercato/shared": "0.4.6-canary-c15f8174eb",
|
|
211
211
|
"@types/html-to-text": "^9.0.4",
|
|
212
212
|
"@types/semver": "^7.5.8",
|
|
213
213
|
"@xyflow/react": "^12.6.0",
|
|
@@ -228,5 +228,6 @@
|
|
|
228
228
|
},
|
|
229
229
|
"publishConfig": {
|
|
230
230
|
"access": "public"
|
|
231
|
-
}
|
|
231
|
+
},
|
|
232
|
+
"stableVersion": "0.4.5"
|
|
232
233
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export const features = [
|
|
2
|
-
'messages.view',
|
|
3
|
-
'messages.compose',
|
|
4
|
-
'messages.attach',
|
|
5
|
-
'messages.attach_files',
|
|
6
|
-
'messages.email',
|
|
7
|
-
'messages.actions',
|
|
8
|
-
'messages.manage',
|
|
2
|
+
{ id: 'messages.view', title: 'View messages', module: 'messages' },
|
|
3
|
+
{ id: 'messages.compose', title: 'Compose messages', module: 'messages' },
|
|
4
|
+
{ id: 'messages.attach', title: 'Attach records to messages', module: 'messages' },
|
|
5
|
+
{ id: 'messages.attach_files', title: 'Attach files to messages', module: 'messages' },
|
|
6
|
+
{ id: 'messages.email', title: 'Send messages via email', module: 'messages' },
|
|
7
|
+
{ id: 'messages.actions', title: 'Execute message actions', module: 'messages' },
|
|
8
|
+
{ id: 'messages.manage', title: 'Manage all messages', module: 'messages' },
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
export default features
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
2
2
|
import { MessageDetailPageClient } from '../../../components/MessageDetailPageClient'
|
|
3
|
+
import { resolveCanViewMessagesForCurrentUser } from '../../../lib/access'
|
|
3
4
|
|
|
4
|
-
export default function MessageDetailPage({ params }: { params: { id: string } }) {
|
|
5
|
+
export default async function MessageDetailPage({ params }: { params: { id: string } }) {
|
|
6
|
+
const canViewMessages = await resolveCanViewMessagesForCurrentUser()
|
|
5
7
|
return (
|
|
6
8
|
<Page>
|
|
7
9
|
<PageBody>
|
|
8
|
-
<MessageDetailPageClient id={params.id} />
|
|
10
|
+
<MessageDetailPageClient id={params.id} canViewMessages={canViewMessages} />
|
|
9
11
|
</PageBody>
|
|
10
12
|
</Page>
|
|
11
13
|
)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
2
2
|
import { ComposeMessagePageClient } from '../../../components/ComposeMessagePageClient'
|
|
3
|
+
import { resolveCanViewMessagesForCurrentUser } from '../../../lib/access'
|
|
3
4
|
|
|
4
|
-
export default function ComposeMessagePage() {
|
|
5
|
+
export default async function ComposeMessagePage() {
|
|
6
|
+
const canViewMessages = await resolveCanViewMessagesForCurrentUser()
|
|
5
7
|
return (
|
|
6
8
|
<Page>
|
|
7
9
|
<PageBody>
|
|
8
|
-
<ComposeMessagePageClient />
|
|
10
|
+
<ComposeMessagePageClient canViewMessages={canViewMessages} />
|
|
9
11
|
</PageBody>
|
|
10
12
|
</Page>
|
|
11
13
|
)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
2
2
|
import { MessagesInboxPageClient } from '../components/MessagesInboxPageClient'
|
|
3
|
+
import { resolveCanViewMessagesForCurrentUser } from '../lib/access'
|
|
4
|
+
|
|
5
|
+
export default async function MessagesInboxPage() {
|
|
6
|
+
const canViewMessages = await resolveCanViewMessagesForCurrentUser()
|
|
3
7
|
|
|
4
|
-
export default function MessagesInboxPage() {
|
|
5
8
|
return (
|
|
6
9
|
<Page>
|
|
7
10
|
<PageBody>
|
|
8
|
-
<MessagesInboxPageClient />
|
|
11
|
+
<MessagesInboxPageClient canViewMessages={canViewMessages} />
|
|
9
12
|
</PageBody>
|
|
10
13
|
</Page>
|
|
11
14
|
)
|
|
@@ -2,9 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import { useRouter } from 'next/navigation'
|
|
4
4
|
import { MessageComposer } from '@open-mercato/ui/backend/messages'
|
|
5
|
+
import { ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
7
|
|
|
6
|
-
export function ComposeMessagePageClient() {
|
|
8
|
+
export function ComposeMessagePageClient({ canViewMessages = true }: { canViewMessages?: boolean }) {
|
|
7
9
|
const router = useRouter()
|
|
10
|
+
const t = useT()
|
|
11
|
+
|
|
12
|
+
if (!canViewMessages) {
|
|
13
|
+
return (
|
|
14
|
+
<ErrorMessage
|
|
15
|
+
label={t('messages.access.disabled.title', 'Messages module is disabled for your role.')}
|
|
16
|
+
description={t(
|
|
17
|
+
'messages.access.disabled.description',
|
|
18
|
+
'Ask your administrator to enable the required Messages permissions.',
|
|
19
|
+
)}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
8
23
|
|
|
9
24
|
return (
|
|
10
25
|
<div className="space-y-4">
|
|
@@ -5,6 +5,7 @@ import { MessageComposer } from '@open-mercato/ui/backend/messages'
|
|
|
5
5
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
6
6
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
7
7
|
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
8
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
8
9
|
import {
|
|
9
10
|
getMessageUiComponentRegistry,
|
|
10
11
|
} from './utils/typeUiRegistry'
|
|
@@ -136,7 +137,7 @@ function MessageConversationDetailItem({
|
|
|
136
137
|
)
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
|
|
140
|
+
function MessageDetailPageClientContent({ id }: { id: string }) {
|
|
140
141
|
const state = useMessageDetails(id)
|
|
141
142
|
const [activeInlineComposer, setActiveInlineComposer] = React.useState<{
|
|
142
143
|
variant: 'reply' | 'forward'
|
|
@@ -285,3 +286,19 @@ export function MessageDetailPageClient({ id }: { id: string }) {
|
|
|
285
286
|
</div>
|
|
286
287
|
)
|
|
287
288
|
}
|
|
289
|
+
|
|
290
|
+
export function MessageDetailPageClient({ id, canViewMessages = true }: { id: string; canViewMessages?: boolean }) {
|
|
291
|
+
const t = useT()
|
|
292
|
+
if (!canViewMessages) {
|
|
293
|
+
return (
|
|
294
|
+
<ErrorMessage
|
|
295
|
+
label={t('messages.access.disabled.title', 'Messages module is disabled for your role.')}
|
|
296
|
+
description={t(
|
|
297
|
+
'messages.access.disabled.description',
|
|
298
|
+
'Ask your administrator to enable the required Messages permissions.',
|
|
299
|
+
)}
|
|
300
|
+
/>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
return <MessageDetailPageClientContent id={id} />
|
|
304
|
+
}
|
|
@@ -9,6 +9,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
9
9
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
10
10
|
import { DataTable } from '@open-mercato/ui/backend/DataTable'
|
|
11
11
|
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
12
|
+
import { ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
12
13
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
13
14
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
14
15
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
@@ -47,6 +48,15 @@ type MessageListResponse = {
|
|
|
47
48
|
totalPages?: number
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
type MessageListQueryResult = {
|
|
52
|
+
items: MessageListItem[]
|
|
53
|
+
total: number
|
|
54
|
+
page: number
|
|
55
|
+
pageSize: number
|
|
56
|
+
totalPages: number
|
|
57
|
+
accessDenied: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
type MessageTypeItem = {
|
|
51
61
|
type: string
|
|
52
62
|
module: string
|
|
@@ -85,7 +95,7 @@ function toErrorMessage(payload: unknown): string | null {
|
|
|
85
95
|
return null
|
|
86
96
|
}
|
|
87
97
|
|
|
88
|
-
export function MessagesInboxPageClient() {
|
|
98
|
+
export function MessagesInboxPageClient({ canViewMessages = true }: { canViewMessages?: boolean }) {
|
|
89
99
|
const router = useRouter()
|
|
90
100
|
const t = useT()
|
|
91
101
|
const scopeVersion = useOrganizationScopeVersion()
|
|
@@ -110,6 +120,7 @@ export function MessagesInboxPageClient() {
|
|
|
110
120
|
JSON.stringify(filterValues),
|
|
111
121
|
scopeVersion,
|
|
112
122
|
],
|
|
123
|
+
enabled: canViewMessages,
|
|
113
124
|
queryFn: async () => {
|
|
114
125
|
const params = new URLSearchParams()
|
|
115
126
|
params.set('folder', folder)
|
|
@@ -138,6 +149,16 @@ export function MessagesInboxPageClient() {
|
|
|
138
149
|
|
|
139
150
|
const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)
|
|
140
151
|
if (!call.ok) {
|
|
152
|
+
if (call.status === 403) {
|
|
153
|
+
return {
|
|
154
|
+
items: [],
|
|
155
|
+
total: 0,
|
|
156
|
+
page,
|
|
157
|
+
pageSize,
|
|
158
|
+
totalPages: 1,
|
|
159
|
+
accessDenied: true,
|
|
160
|
+
} satisfies MessageListQueryResult
|
|
161
|
+
}
|
|
141
162
|
throw new Error(
|
|
142
163
|
toErrorMessage(call.result)
|
|
143
164
|
?? t('messages.errors.loadListFailed', 'Failed to load messages.'),
|
|
@@ -150,15 +171,18 @@ export function MessagesInboxPageClient() {
|
|
|
150
171
|
page: Number(call.result?.page ?? page),
|
|
151
172
|
pageSize: Number(call.result?.pageSize ?? pageSize),
|
|
152
173
|
totalPages: Number(call.result?.totalPages ?? 1),
|
|
153
|
-
|
|
174
|
+
accessDenied: false,
|
|
175
|
+
} satisfies MessageListQueryResult
|
|
154
176
|
},
|
|
155
177
|
})
|
|
156
178
|
|
|
157
179
|
const messageTypesQuery = useQuery({
|
|
158
180
|
queryKey: ['messages', 'types', scopeVersion],
|
|
181
|
+
enabled: canViewMessages,
|
|
159
182
|
queryFn: async () => {
|
|
160
183
|
const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')
|
|
161
184
|
if (!call.ok) {
|
|
185
|
+
if (call.status === 403) return []
|
|
162
186
|
throw new Error(
|
|
163
187
|
toErrorMessage(call.result)
|
|
164
188
|
?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),
|
|
@@ -169,16 +193,19 @@ export function MessagesInboxPageClient() {
|
|
|
169
193
|
})
|
|
170
194
|
|
|
171
195
|
React.useEffect(() => {
|
|
196
|
+
if (!canViewMessages) return
|
|
172
197
|
if (!listQuery.error) return
|
|
198
|
+
if (listQuery.data?.accessDenied) return
|
|
173
199
|
flash(
|
|
174
200
|
listQuery.error instanceof Error
|
|
175
201
|
? listQuery.error.message
|
|
176
202
|
: t('messages.errors.loadListFailed', 'Failed to load messages.'),
|
|
177
203
|
'error',
|
|
178
204
|
)
|
|
179
|
-
}, [listQuery.error, t])
|
|
205
|
+
}, [canViewMessages, listQuery.error, t])
|
|
180
206
|
|
|
181
207
|
React.useEffect(() => {
|
|
208
|
+
if (!canViewMessages) return
|
|
182
209
|
if (!messageTypesQuery.error) return
|
|
183
210
|
flash(
|
|
184
211
|
messageTypesQuery.error instanceof Error
|
|
@@ -186,7 +213,7 @@ export function MessagesInboxPageClient() {
|
|
|
186
213
|
: t('messages.errors.loadTypesFailed', 'Failed to load message types.'),
|
|
187
214
|
'error',
|
|
188
215
|
)
|
|
189
|
-
}, [messageTypesQuery.error, t])
|
|
216
|
+
}, [canViewMessages, messageTypesQuery.error, t])
|
|
190
217
|
|
|
191
218
|
const messageTypeLabelMap = React.useMemo(() => {
|
|
192
219
|
const map: Record<string, string> = {}
|
|
@@ -373,10 +400,25 @@ export function MessagesInboxPageClient() {
|
|
|
373
400
|
}
|
|
374
401
|
}, [folderMenuOpen])
|
|
375
402
|
|
|
403
|
+
const accessDenied = !canViewMessages || listQuery.data?.accessDenied === true
|
|
376
404
|
const rows = listQuery.data?.items ?? []
|
|
377
405
|
const total = listQuery.data?.total ?? 0
|
|
378
406
|
const totalPages = listQuery.data?.totalPages ?? 1
|
|
379
407
|
|
|
408
|
+
if (accessDenied) {
|
|
409
|
+
return (
|
|
410
|
+
<div className="space-y-4">
|
|
411
|
+
<ErrorMessage
|
|
412
|
+
label={t('messages.access.disabled.title', 'Messages module is disabled for your role.')}
|
|
413
|
+
description={t(
|
|
414
|
+
'messages.access.disabled.description',
|
|
415
|
+
'Ask your administrator to enable the required Messages permissions.',
|
|
416
|
+
)}
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
380
422
|
return (
|
|
381
423
|
<div className="space-y-4">
|
|
382
424
|
<DataTable
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"messages.actions.archive": "Archivieren",
|
|
3
3
|
"messages.actions.archiveConversation": "Konversation archivieren",
|
|
4
4
|
"messages.actions.backToList": "Zurück zu Nachrichten",
|
|
5
|
+
"messages.access.disabled.description": "Bitte wenden Sie sich an Ihren Administrator, um die erforderlichen Nachrichten-Berechtigungen zu aktivieren.",
|
|
6
|
+
"messages.access.disabled.title": "Das Nachrichtenmodul ist für Ihre Rolle deaktiviert.",
|
|
5
7
|
"messages.actions.confirmation": "Bestätigen",
|
|
6
8
|
"messages.actions.conversationActions": "Konversationsaktionen",
|
|
7
9
|
"messages.actions.delete": "Löschen",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"messages.actions.archive": "Archive",
|
|
3
3
|
"messages.actions.archiveConversation": "Archive conversation",
|
|
4
4
|
"messages.actions.backToList": "Back to messages",
|
|
5
|
+
"messages.access.disabled.description": "Ask your administrator to enable the required Messages permissions.",
|
|
6
|
+
"messages.access.disabled.title": "Messages module is disabled for your role.",
|
|
5
7
|
"messages.actions.confirmation": "Confirm",
|
|
6
8
|
"messages.actions.conversationActions": "Conversation actions",
|
|
7
9
|
"messages.actions.delete": "Delete",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"messages.actions.archive": "Archivar",
|
|
3
3
|
"messages.actions.archiveConversation": "Archivar conversación",
|
|
4
4
|
"messages.actions.backToList": "Volver a mensajes",
|
|
5
|
+
"messages.access.disabled.description": "Pide a tu administrador que habilite los permisos requeridos de Mensajes.",
|
|
6
|
+
"messages.access.disabled.title": "El módulo de mensajes está deshabilitado para tu rol.",
|
|
5
7
|
"messages.actions.confirmation": "Confirmar",
|
|
6
8
|
"messages.actions.conversationActions": "Acciones de conversación",
|
|
7
9
|
"messages.actions.delete": "Eliminar",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"messages.actions.archive": "Archiwizuj",
|
|
3
3
|
"messages.actions.archiveConversation": "Archiwizuj konwersację",
|
|
4
4
|
"messages.actions.backToList": "Powrót do wiadomości",
|
|
5
|
+
"messages.access.disabled.description": "Poproś administratora o włączenie wymaganych uprawnień modułu wiadomości.",
|
|
6
|
+
"messages.access.disabled.title": "Moduł wiadomości jest wyłączony dla Twojej roli.",
|
|
5
7
|
"messages.actions.confirmation": "Potwierdź",
|
|
6
8
|
"messages.actions.conversationActions": "Akcje konwersacji",
|
|
7
9
|
"messages.actions.delete": "Usuń",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { cookies } from 'next/headers'
|
|
2
|
+
import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
5
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
6
|
+
|
|
7
|
+
export async function resolveCanViewMessagesForCurrentUser(): Promise<boolean> {
|
|
8
|
+
const auth = await getAuthFromCookies()
|
|
9
|
+
if (!auth) return false
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const cookieStore = await cookies()
|
|
13
|
+
const rawSelectedOrg = cookieStore.get('om_selected_org')?.value
|
|
14
|
+
const rawSelectedTenant = cookieStore.get('om_selected_tenant')?.value
|
|
15
|
+
const selectedOrgForScope = rawSelectedOrg === undefined
|
|
16
|
+
? undefined
|
|
17
|
+
: rawSelectedOrg && rawSelectedOrg.trim().length > 0
|
|
18
|
+
? rawSelectedOrg
|
|
19
|
+
: null
|
|
20
|
+
const selectedTenantForScope = rawSelectedTenant === undefined
|
|
21
|
+
? undefined
|
|
22
|
+
: rawSelectedTenant && rawSelectedTenant.trim().length > 0
|
|
23
|
+
? rawSelectedTenant
|
|
24
|
+
: null
|
|
25
|
+
|
|
26
|
+
const container = await createRequestContainer()
|
|
27
|
+
const rbac = container.resolve<RbacService>('rbacService')
|
|
28
|
+
const { organizationId, scope, allowedOrganizationIds } = await resolveFeatureCheckContext({
|
|
29
|
+
container,
|
|
30
|
+
auth,
|
|
31
|
+
selectedId: selectedOrgForScope,
|
|
32
|
+
tenantId: selectedTenantForScope,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return await rbac.userHasAllFeatures(auth.sub, ['messages.view'], {
|
|
40
|
+
tenantId: scope.tenantId ?? auth.tenantId ?? null,
|
|
41
|
+
organizationId: organizationId ?? null,
|
|
42
|
+
})
|
|
43
|
+
} catch {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
}
|