@open-mercato/core 0.4.6-develop-18ffe6bc13 → 0.4.6-develop-06c0791dc9
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/dist/modules/messages/backend/messages/[id]/page.js +1 -3
- package/dist/modules/messages/backend/messages/[id]/page.js.map +2 -2
- package/dist/modules/messages/backend/messages/compose/page.js +1 -3
- package/dist/modules/messages/backend/messages/compose/page.js.map +2 -2
- package/dist/modules/messages/backend/page.js +1 -3
- package/dist/modules/messages/backend/page.js.map +2 -2
- package/dist/modules/messages/components/ComposeMessagePageClient.js +1 -16
- package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +1 -15
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +4 -8
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/messages/backend/messages/[id]/page.tsx +1 -3
- package/src/modules/messages/backend/messages/compose/page.tsx +1 -3
- package/src/modules/messages/backend/page.tsx +1 -4
- package/src/modules/messages/components/ComposeMessagePageClient.tsx +1 -16
- package/src/modules/messages/components/MessageDetailPageClient.tsx +1 -13
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -8
- package/dist/modules/messages/lib/access.js +0 -36
- package/dist/modules/messages/lib/access.js.map +0 -7
- package/src/modules/messages/lib/access.ts +0 -46
|
@@ -1,10 +1,8 @@
|
|
|
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
|
-
import { resolveCanViewMessagesForCurrentUser } from "../../../lib/access.js";
|
|
5
4
|
async function MessageDetailPage({ params }) {
|
|
6
|
-
|
|
7
|
-
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(MessageDetailPageClient, { id: params.id, canViewMessages }) }) });
|
|
5
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(MessageDetailPageClient, { id: params.id }) }) });
|
|
8
6
|
}
|
|
9
7
|
export {
|
|
10
8
|
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'\
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { MessageDetailPageClient } from '../../../components/MessageDetailPageClient'\n\nexport default async 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": "AAOQ;AAPR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,+BAA+B;AAExC,eAAO,kBAAyC,EAAE,OAAO,GAA+B;AACtF,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,2BAAwB,IAAI,OAAO,IAAI,GAC1C,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
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
|
-
import { resolveCanViewMessagesForCurrentUser } from "../../../lib/access.js";
|
|
5
4
|
async function ComposeMessagePage() {
|
|
6
|
-
|
|
7
|
-
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ComposeMessagePageClient, { canViewMessages }) }) });
|
|
5
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ComposeMessagePageClient, {}) }) });
|
|
8
6
|
}
|
|
9
7
|
export {
|
|
10
8
|
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'\
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { ComposeMessagePageClient } from '../../../components/ComposeMessagePageClient'\n\nexport default async function ComposeMessagePage() {\n return (\n <Page>\n <PageBody>\n <ComposeMessagePageClient />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "AAOQ;AAPR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gCAAgC;AAEzC,eAAO,qBAA4C;AACjD,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,4BAAyB,GAC5B,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
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
|
-
import { resolveCanViewMessagesForCurrentUser } from "../lib/access.js";
|
|
5
4
|
async function MessagesInboxPage() {
|
|
6
|
-
|
|
7
|
-
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(MessagesInboxPageClient, { canViewMessages }) }) });
|
|
5
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(MessagesInboxPageClient, {}) }) });
|
|
8
6
|
}
|
|
9
7
|
export {
|
|
10
8
|
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'\
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { MessagesInboxPageClient } from '../components/MessagesInboxPageClient'\n\nexport default async function MessagesInboxPage() {\n return (\n <Page>\n <PageBody>\n <MessagesInboxPageClient />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "AAOQ;AAPR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,+BAA+B;AAExC,eAAO,oBAA2C;AAChD,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,2BAAwB,GAC3B,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,23 +2,8 @@
|
|
|
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
|
-
|
|
6
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
|
-
function ComposeMessagePageClient({ canViewMessages = true }) {
|
|
5
|
+
function ComposeMessagePageClient() {
|
|
8
6
|
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
|
-
}
|
|
22
7
|
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
23
8
|
MessageComposer,
|
|
24
9
|
{
|
|
@@ -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'\
|
|
5
|
-
"mappings": ";
|
|
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": ";AAUM;AARN,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAEzB,SAAS,2BAA2B;AACzC,QAAM,SAAS,UAAU;AAEzB,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,7 +5,6 @@ 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";
|
|
9
8
|
import {
|
|
10
9
|
getMessageUiComponentRegistry
|
|
11
10
|
} from "./utils/typeUiRegistry.js";
|
|
@@ -256,20 +255,7 @@ function MessageDetailPageClientContent({ id }) {
|
|
|
256
255
|
ConfirmDialogElement
|
|
257
256
|
] });
|
|
258
257
|
}
|
|
259
|
-
function MessageDetailPageClient({ id
|
|
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
|
-
}
|
|
258
|
+
function MessageDetailPageClient({ id }) {
|
|
273
259
|
return /* @__PURE__ */ jsx(MessageDetailPageClientContent, { id });
|
|
274
260
|
}
|
|
275
261
|
export {
|
|
@@ -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 { 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": ";AA8CQ,cAuBA,YAvBA;AA5CR,YAAY,WAAW;AACvB,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,cAAc;AACvB,SAAS,gBAAgB,oBAAoB;
|
|
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 }: { id: string }) {\n return <MessageDetailPageClientContent id={id} />\n}\n"],
|
|
5
|
+
"mappings": ";AA8CQ,cAuBA,YAvBA;AA5CR,YAAY,WAAW;AACvB,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,cAAc;AACvB,SAAS,gBAAgB,oBAAoB;AAE7C;AAAA,EACE;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAElC,SAAS,8BAA8B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,QAAQ,kBAAkB,SAAS;AACzC,QAAM,oBAAoB,MAAM,QAAQ,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAEjF,MAAI,MAAM,iBAAiB;AACzB,WACE,oBAAC,aAAQ,WAAU,QACjB,8BAAC,kBAAe,OAAO,MAAM,EAAE,2BAA2B,oBAAoB,GAAG,GACnF;AAAA,EAEJ;AAEA,MAAI,CAAC,MAAM,QAAQ;AACjB,WACE,oBAAC,aAAQ,WAAU,QACjB,8BAAC,gBAAa,OAAO,MAAM,kBAAkB,GAC/C;AAAA,EAEJ;AAEA,QAAM,mBAAmB,MAAM,sBAC3B,kBAAkB,kBAAkB,MAAM,mBAAmB,KAAK,OAClE;AACJ,QAAM,mBAAmB,MAAM,sBAC3B,kBAAkB,kBAAkB,MAAM,mBAAmB,KAAK,OAClE;AAEJ,SACE,qBAAC,aAAQ,WAAU,QACjB;AAAA,yBAAC,SAAI,WAAU,aACb;AAAA,2BAAC,aAAQ,WAAU,kBACjB;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,QAAQ,MAAM;AAAA,YACd,eAAe,MAAM;AAAA,YACrB,YAAY,MAAM;AAAA,YAClB,aAAa;AAAA,YACb,gBAAgB,gBAAgB,EAAE,UAAU,YAAY,SAAS,IAAI;AAAA,YACrE,SAAS,MAAM,QAAQ,SAAS;AAAA,YAChC,WAAW,MAAM,UAAU,SAAS;AAAA,YACpC,QAAQ,MAAM,MAAM,YAAY,IAAI;AAAA,YACpC,cAAc,MAAM,KAAK,MAAM,WAAW;AAAA,YAC1C,iBAAiB,MAAM,KAAK,MAAM,cAAc;AAAA,YAChD,UAAU,MAAM,MAAM,0BAA0B,IAAI;AAAA;AAAA,QACtD;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,QAAQ,MAAM;AAAA,YACd,cAAc,MAAM;AAAA,YACpB;AAAA;AAAA,QACF;AAAA,QAEA,oBAAC,4BAAyB,QAAQ,MAAM,QAAQ;AAAA,SAClD;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ,MAAM;AAAA,UACd,gBAAgB,MAAM;AAAA,UACtB,mBAAmB,MAAM;AAAA,UACzB;AAAA,UACA,qBAAqB,MAAM;AAAA,UAC3B,iBAAiB,MAAM;AAAA;AAAA,MACzB;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ,MAAM;AAAA,UACd,yBAAyB,MAAM;AAAA,UAC/B,iBAAiB,MAAM;AAAA;AAAA,MACzB;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,kBAAkB,MAAM;AAAA,UACxB,aAAa,MAAM;AAAA;AAAA,MACrB;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,IAAI;AAAA,QACJ,QAAQ,MAAM;AAAA,QACd,aAAa,MAAM;AAAA,QACnB,UAAU,MAAM;AAAA,QAChB,aAAa,MAAM;AAAA,QACnB,WAAW,MAAM,MAAM,YAAY,QAAQ;AAAA;AAAA,IAC7C;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,2BAA2B,MAAM;AAAA,QACjC,8BAA8B,MAAM;AAAA,QACpC,mBAAmB,MAAM;AAAA,QACzB,4BAA4B,MAAM;AAAA,QAClC,kCAAkC,MAAM;AAAA,QACxC,wBAAwB,MAAM;AAAA,QAC9B,2BAA2B,MAAM;AAAA,QACjC,eAAe,MAAM;AAAA,QACrB,cAAc,MAAM;AAAA,QACpB,2BAA2B,MAAM;AAAA;AAAA,IACnC;AAAA,KACF;AAEJ;AAEA,SAAS,+BAA+B,EAAE,GAAG,GAAmB;AAC9D,QAAM,QAAQ,kBAAkB,EAAE;AAClC,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,MAAM,SAGpD,IAAI;AACd,QAAM,6BAA6B,MAAM,OAA8B,IAAI;AAC3E,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,qBAAsB;AAE3B,UAAM,oBAAoB,2BAA2B;AACrD,QAAI,CAAC,kBAAmB;AAExB,UAAM,wBAAwB,kBAAkB;AAAA,MAC9C;AAAA,IACF;AACA,2BAAuB,MAAM;AAAA,EAC/B,GAAG,CAAC,oBAAoB,CAAC;AAEzB,MAAI,MAAM,iBAAiB;AACzB,WAAO,oBAAC,kBAAe,OAAO,MAAM,EAAE,2BAA2B,oBAAoB,GAAG;AAAA,EAC1F;AAEA,MAAI,CAAC,MAAM,QAAQ;AACjB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,MAAM;AAAA,QACb,QACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,YACpD,gBAAM,EAAE,+BAA+B,kBAAkB,GAC5D;AAAA;AAAA,IAEJ;AAAA,EAEJ;AAEA,QAAM,SAAS,MAAM;AACrB,QAAM,6BAA6B,MAAM,kBAAkB,CAAC,GAAG,MAAM;AACrE,QAAM,8BAA8B,MAAM,kBAAkB,MAAM,kBAAkB,SAAS,CAAC,GAAG,MAAM;AACvG,QAAM,4BAA4B,QAAQ,0BAA0B;AAEpE,SACE,qBAAC,SAAI,WAAU,aACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,OAAO;AAAA,QAChB,UAAW,OAAO,YAAqD;AAAA,QACvE,UAAU,CAAC,OAAO,WAAW,OAAO,eAAe,cAAc,QAAQ,0BAA0B;AAAA,QACnG,eAAe,CAAC,OAAO,WAAW,OAAO,eAAe,gBAAgB,QAAQ,2BAA2B;AAAA,QAC3G,iBAAiB,QAAQ,MAAM,wBAAwB;AAAA,QACvD,gBAAgB,MAAM;AAAA,QACtB,SAAS,MAAM;AACb,cAAI,CAAC,2BAA4B;AACjC,kCAAwB;AAAA,YACtB,SAAS;AAAA,YACT,WAAW;AAAA,UACb,CAAC;AAAA,QACH;AAAA,QACA,cAAc,MAAM;AAClB,cAAI,CAAC,4BAA6B;AAClC,kCAAwB;AAAA,YACtB,SAAS;AAAA,YACT,WAAW;AAAA,UACb,CAAC;AAAA,QACH;AAAA,QACA,uBAAuB,MAAM;AAC3B,cAAI,CAAC,0BAA2B;AAChC,eAAK,MAAM,oBAAoB,8BAA8B,MAAS;AAAA,QACxE;AAAA,QACA,iBAAiB,MAAM;AACrB,cAAI,CAAC,0BAA2B;AAChC,eAAK,MAAM,uBAAuB,8BAA8B,MAAS;AAAA,QAC3E;AAAA,QACA,sBAAsB,MAAM;AAC1B,cAAI,CAAC,6BAA6B,MAAM,yBAA0B;AAClE,gBAAM,YAAY;AAChB,kBAAM,YAAY,MAAM,QAAQ;AAAA,cAC9B,OAAO,MAAM,EAAE,4CAA4C,qBAAqB;AAAA,cAChF,MAAM,MAAM,EAAE,uCAAuC,0CAA0C;AAAA,cAC/F,aAAa,MAAM,EAAE,uCAAuC,qBAAqB;AAAA,cACjF,YAAY,MAAM,EAAE,iBAAiB,QAAQ;AAAA,cAC7C,SAAS;AAAA,YACX,CAAC;AACD,gBAAI,CAAC,UAAW;AAChB,kBAAM,MAAM,mBAAmB,8BAA8B,MAAS;AAAA,UACxE,GAAG;AAAA,QACL;AAAA;AAAA,IACF;AAAA,IACA,oBAAC,SAAI,WAAU,qBACZ,gBAAM,kBAAkB,IAAI,CAAC,SAAS;AACrC,YAAM,mBAAmB,KAAK,OAAO,MAAM;AAC3C,YAAM,aAAa,MAAM,2BAA2B,KAAK,EAAE;AAC3D,UAAI,YAAY;AACd,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,KAAK;AAAA,YAChB,eAAe,CAAC;AAAA,YAChB,YAAU;AAAA,YACV,UAAU,MAAM,MAAM,uBAAuB,KAAK,EAAE;AAAA,YACpD,SAAS,CAAC,cAAc;AACtB,sCAAwB;AAAA,gBACtB,SAAS;AAAA,gBACT;AAAA,cACF,CAAC;AAAA,YACH;AAAA,YACA,WAAW,CAAC,cAAc;AACxB,sCAAwB;AAAA,gBACtB,SAAS;AAAA,gBACT;AAAA,cACF,CAAC;AAAA,YACH;AAAA;AAAA,UAhBK,KAAK;AAAA,QAiBZ;AAAA,MAEJ;AAEA,aACE,oBAAC,aAAsB,WAAU,aAC/B;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,iCAAiC,IAAI;AAAA,UACpD,SAAS,MAAM,MAAM,uBAAuB,KAAK,EAAE;AAAA;AAAA,MACrD,KAJY,KAAK,EAKnB;AAAA,IAEJ,CAAC,GACH;AAAA,IAEC,uBACC,oBAAC,SAAI,KAAK,4BACR;AAAA,MAAC;AAAA;AAAA,QACC,QAAM;AAAA,QACN,gBAAgB;AAAA,QAChB,SAAS,qBAAqB;AAAA,QAC9B,WAAW,qBAAqB;AAAA,QAChC,UAAU,MAAM;AACd,kCAAwB,IAAI;AAAA,QAC9B;AAAA,QACA,WAAW,MAAM;AACf,kCAAwB,IAAI;AAC5B,eAAK,MAAM,YAAY,QAAQ;AAAA,QACjC;AAAA;AAAA,IACF,GACF,IACE;AAAA,IACH;AAAA,KACH;AAEJ;AAEO,SAAS,wBAAwB,EAAE,GAAG,GAAmB;AAC9D,SAAO,oBAAC,kCAA+B,IAAQ;AACjD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -30,7 +30,7 @@ function toErrorMessage(payload) {
|
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
-
function MessagesInboxPageClient(
|
|
33
|
+
function MessagesInboxPageClient() {
|
|
34
34
|
const router = useRouter();
|
|
35
35
|
const t = useT();
|
|
36
36
|
const scopeVersion = useOrganizationScopeVersion();
|
|
@@ -53,7 +53,6 @@ function MessagesInboxPageClient({ canViewMessages = true }) {
|
|
|
53
53
|
JSON.stringify(filterValues),
|
|
54
54
|
scopeVersion
|
|
55
55
|
],
|
|
56
|
-
enabled: canViewMessages,
|
|
57
56
|
queryFn: async () => {
|
|
58
57
|
const params = new URLSearchParams();
|
|
59
58
|
params.set("folder", folder);
|
|
@@ -104,7 +103,6 @@ function MessagesInboxPageClient({ canViewMessages = true }) {
|
|
|
104
103
|
});
|
|
105
104
|
const messageTypesQuery = useQuery({
|
|
106
105
|
queryKey: ["messages", "types", scopeVersion],
|
|
107
|
-
enabled: canViewMessages,
|
|
108
106
|
queryFn: async () => {
|
|
109
107
|
const call = await apiCall("/api/messages/types");
|
|
110
108
|
if (!call.ok) {
|
|
@@ -117,22 +115,20 @@ function MessagesInboxPageClient({ canViewMessages = true }) {
|
|
|
117
115
|
}
|
|
118
116
|
});
|
|
119
117
|
React.useEffect(() => {
|
|
120
|
-
if (!canViewMessages) return;
|
|
121
118
|
if (!listQuery.error) return;
|
|
122
119
|
if (listQuery.data?.accessDenied) return;
|
|
123
120
|
flash(
|
|
124
121
|
listQuery.error instanceof Error ? listQuery.error.message : t("messages.errors.loadListFailed", "Failed to load messages."),
|
|
125
122
|
"error"
|
|
126
123
|
);
|
|
127
|
-
}, [
|
|
124
|
+
}, [listQuery.error, listQuery.data?.accessDenied, t]);
|
|
128
125
|
React.useEffect(() => {
|
|
129
|
-
if (!canViewMessages) return;
|
|
130
126
|
if (!messageTypesQuery.error) return;
|
|
131
127
|
flash(
|
|
132
128
|
messageTypesQuery.error instanceof Error ? messageTypesQuery.error.message : t("messages.errors.loadTypesFailed", "Failed to load message types."),
|
|
133
129
|
"error"
|
|
134
130
|
);
|
|
135
|
-
}, [
|
|
131
|
+
}, [messageTypesQuery.error, t]);
|
|
136
132
|
const messageTypeLabelMap = React.useMemo(() => {
|
|
137
133
|
const map = {};
|
|
138
134
|
for (const item of messageTypesQuery.data ?? []) {
|
|
@@ -305,7 +301,7 @@ function MessagesInboxPageClient({ canViewMessages = true }) {
|
|
|
305
301
|
document.removeEventListener("keydown", handleEscape);
|
|
306
302
|
};
|
|
307
303
|
}, [folderMenuOpen]);
|
|
308
|
-
const accessDenied =
|
|
304
|
+
const accessDenied = listQuery.data?.accessDenied === true;
|
|
309
305
|
const rows = listQuery.data?.items ?? [];
|
|
310
306
|
const total = listQuery.data?.total ?? 0;
|
|
311
307
|
const totalPages = listQuery.data?.totalPages ?? 1;
|
|
@@ -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 { 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": "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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() {\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 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 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 (!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 }, [listQuery.error, listQuery.data?.accessDenied, 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 accessDenied = 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": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.6-develop-
|
|
3
|
+
"version": "0.4.6-develop-06c0791dc9",
|
|
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.6-develop-
|
|
210
|
+
"@open-mercato/shared": "0.4.6-develop-06c0791dc9",
|
|
211
211
|
"@types/html-to-text": "^9.0.4",
|
|
212
212
|
"@types/semver": "^7.5.8",
|
|
213
213
|
"@xyflow/react": "^12.6.0",
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
2
2
|
import { MessageDetailPageClient } from '../../../components/MessageDetailPageClient'
|
|
3
|
-
import { resolveCanViewMessagesForCurrentUser } from '../../../lib/access'
|
|
4
3
|
|
|
5
4
|
export default async function MessageDetailPage({ params }: { params: { id: string } }) {
|
|
6
|
-
const canViewMessages = await resolveCanViewMessagesForCurrentUser()
|
|
7
5
|
return (
|
|
8
6
|
<Page>
|
|
9
7
|
<PageBody>
|
|
10
|
-
<MessageDetailPageClient id={params.id}
|
|
8
|
+
<MessageDetailPageClient id={params.id} />
|
|
11
9
|
</PageBody>
|
|
12
10
|
</Page>
|
|
13
11
|
)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
2
2
|
import { ComposeMessagePageClient } from '../../../components/ComposeMessagePageClient'
|
|
3
|
-
import { resolveCanViewMessagesForCurrentUser } from '../../../lib/access'
|
|
4
3
|
|
|
5
4
|
export default async function ComposeMessagePage() {
|
|
6
|
-
const canViewMessages = await resolveCanViewMessagesForCurrentUser()
|
|
7
5
|
return (
|
|
8
6
|
<Page>
|
|
9
7
|
<PageBody>
|
|
10
|
-
<ComposeMessagePageClient
|
|
8
|
+
<ComposeMessagePageClient />
|
|
11
9
|
</PageBody>
|
|
12
10
|
</Page>
|
|
13
11
|
)
|
|
@@ -1,14 +1,11 @@
|
|
|
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
3
|
|
|
5
4
|
export default async function MessagesInboxPage() {
|
|
6
|
-
const canViewMessages = await resolveCanViewMessagesForCurrentUser()
|
|
7
|
-
|
|
8
5
|
return (
|
|
9
6
|
<Page>
|
|
10
7
|
<PageBody>
|
|
11
|
-
<MessagesInboxPageClient
|
|
8
|
+
<MessagesInboxPageClient />
|
|
12
9
|
</PageBody>
|
|
13
10
|
</Page>
|
|
14
11
|
)
|
|
@@ -2,24 +2,9 @@
|
|
|
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'
|
|
7
5
|
|
|
8
|
-
export function ComposeMessagePageClient(
|
|
6
|
+
export function ComposeMessagePageClient() {
|
|
9
7
|
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
|
-
}
|
|
23
8
|
|
|
24
9
|
return (
|
|
25
10
|
<div className="space-y-4">
|
|
@@ -287,18 +287,6 @@ function MessageDetailPageClientContent({ id }: { id: string }) {
|
|
|
287
287
|
)
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
export function MessageDetailPageClient({ id
|
|
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
|
-
}
|
|
290
|
+
export function MessageDetailPageClient({ id }: { id: string }) {
|
|
303
291
|
return <MessageDetailPageClientContent id={id} />
|
|
304
292
|
}
|
|
@@ -95,7 +95,7 @@ function toErrorMessage(payload: unknown): string | null {
|
|
|
95
95
|
return null
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export function MessagesInboxPageClient(
|
|
98
|
+
export function MessagesInboxPageClient() {
|
|
99
99
|
const router = useRouter()
|
|
100
100
|
const t = useT()
|
|
101
101
|
const scopeVersion = useOrganizationScopeVersion()
|
|
@@ -120,7 +120,6 @@ export function MessagesInboxPageClient({ canViewMessages = true }: { canViewMes
|
|
|
120
120
|
JSON.stringify(filterValues),
|
|
121
121
|
scopeVersion,
|
|
122
122
|
],
|
|
123
|
-
enabled: canViewMessages,
|
|
124
123
|
queryFn: async () => {
|
|
125
124
|
const params = new URLSearchParams()
|
|
126
125
|
params.set('folder', folder)
|
|
@@ -178,7 +177,6 @@ export function MessagesInboxPageClient({ canViewMessages = true }: { canViewMes
|
|
|
178
177
|
|
|
179
178
|
const messageTypesQuery = useQuery({
|
|
180
179
|
queryKey: ['messages', 'types', scopeVersion],
|
|
181
|
-
enabled: canViewMessages,
|
|
182
180
|
queryFn: async () => {
|
|
183
181
|
const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')
|
|
184
182
|
if (!call.ok) {
|
|
@@ -193,7 +191,6 @@ export function MessagesInboxPageClient({ canViewMessages = true }: { canViewMes
|
|
|
193
191
|
})
|
|
194
192
|
|
|
195
193
|
React.useEffect(() => {
|
|
196
|
-
if (!canViewMessages) return
|
|
197
194
|
if (!listQuery.error) return
|
|
198
195
|
if (listQuery.data?.accessDenied) return
|
|
199
196
|
flash(
|
|
@@ -202,10 +199,9 @@ export function MessagesInboxPageClient({ canViewMessages = true }: { canViewMes
|
|
|
202
199
|
: t('messages.errors.loadListFailed', 'Failed to load messages.'),
|
|
203
200
|
'error',
|
|
204
201
|
)
|
|
205
|
-
}, [
|
|
202
|
+
}, [listQuery.error, listQuery.data?.accessDenied, t])
|
|
206
203
|
|
|
207
204
|
React.useEffect(() => {
|
|
208
|
-
if (!canViewMessages) return
|
|
209
205
|
if (!messageTypesQuery.error) return
|
|
210
206
|
flash(
|
|
211
207
|
messageTypesQuery.error instanceof Error
|
|
@@ -213,7 +209,7 @@ export function MessagesInboxPageClient({ canViewMessages = true }: { canViewMes
|
|
|
213
209
|
: t('messages.errors.loadTypesFailed', 'Failed to load message types.'),
|
|
214
210
|
'error',
|
|
215
211
|
)
|
|
216
|
-
}, [
|
|
212
|
+
}, [messageTypesQuery.error, t])
|
|
217
213
|
|
|
218
214
|
const messageTypeLabelMap = React.useMemo(() => {
|
|
219
215
|
const map: Record<string, string> = {}
|
|
@@ -400,7 +396,7 @@ export function MessagesInboxPageClient({ canViewMessages = true }: { canViewMes
|
|
|
400
396
|
}
|
|
401
397
|
}, [folderMenuOpen])
|
|
402
398
|
|
|
403
|
-
const accessDenied =
|
|
399
|
+
const accessDenied = listQuery.data?.accessDenied === true
|
|
404
400
|
const rows = listQuery.data?.items ?? []
|
|
405
401
|
const total = listQuery.data?.total ?? 0
|
|
406
402
|
const totalPages = listQuery.data?.totalPages ?? 1
|
|
@@ -1,36 +0,0 @@
|
|
|
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
|
|
@@ -1,7 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
}
|