@burdenoff/microfe-conversations 2026.527.2 → 2026.528.2
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/conversations/ConversationsRoot.js +1 -1
- package/dist/conversations/ConversationsRoot.js.map +1 -1
- package/dist/conversations/ConversationsUserRoot.js +1 -1
- package/dist/conversations/ConversationsUserRoot.js.map +1 -1
- package/dist/conversations/ConversationsUserRoutes.js +6 -6
- package/dist/conversations/ConversationsUserRoutes.js.map +1 -1
- package/dist/conversations/components/ConversationRow.js +176 -0
- package/dist/conversations/components/ConversationRow.js.map +1 -0
- package/dist/conversations/components/EmptyState.js +10 -21
- package/dist/conversations/components/EmptyState.js.map +1 -1
- package/dist/conversations/components/InboxLayout.js +189 -0
- package/dist/conversations/components/InboxLayout.js.map +1 -0
- package/dist/conversations/components/NewConversationDialog.js +285 -0
- package/dist/conversations/components/NewConversationDialog.js.map +1 -0
- package/dist/conversations/components/PageLayout.js +16 -34
- package/dist/conversations/components/PageLayout.js.map +1 -1
- package/dist/conversations/components/PageSection.js +10 -19
- package/dist/conversations/components/PageSection.js.map +1 -1
- package/dist/conversations/hooks/useConversationQueries.js +48 -34
- package/dist/conversations/hooks/useConversationQueries.js.map +1 -1
- package/dist/conversations/pages/ConversationListPage.js +187 -226
- package/dist/conversations/pages/ConversationListPage.js.map +1 -1
- package/dist/conversations/pages/ConversationsLandingPage.js +109 -99
- package/dist/conversations/pages/ConversationsLandingPage.js.map +1 -1
- package/dist/conversations/pages/ConversationsSettingsPage.js +201 -182
- package/dist/conversations/pages/ConversationsSettingsPage.js.map +1 -1
- package/dist/conversations/pages/NewConversationPage.js +19 -280
- package/dist/conversations/pages/NewConversationPage.js.map +1 -1
- package/dist/generated/wspace-operations.js +72 -56
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +2 -2
- package/dist/conversations/components/ConversationCard.js +0 -119
- package/dist/conversations/components/ConversationCard.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationsRoot.js","names":[],"sources":["../../src/conversations/ConversationsRoot.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { ErrorBoundary } from \"@burdenoff/fe-libs/shared/components\";\nimport { FeatureFlagProvider } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { MessageCircle, Plus, Settings } from \"lucide-react\";\nimport { ConversationsProvider } from \"./providers/ConversationsProvider\";\nimport type { NavItem, ConversationsRootProps } from \"./types\";\nimport { joinPath } from \"./utils/navigation\";\nimport { useTr } from \"./utils/i18n\";\nimport { ConversationsUserRoutes } from \"./ConversationsUserRoutes\";\n\n/**\n * Root component for the Conversations microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { ConversationsRoot, type ConversationsRootProps } from '@burdenoff/micro-fe/conversations';\n *\n * function App() {\n * const props: ConversationsRootProps = {\n * basePath: '/conversations',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com', firstName: 'John', lastName: 'Doe' },\n * workspaceId: 'ws-123',\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * defaultView: 'inbox',\n * onConversationSelect: (conversation) => console.log('Conversation selected:', conversation),\n * registerNavItems: (items) => { return () => {}; },\n * };\n *\n * return <ConversationsRoot {...props} />;\n * }\n * ```\n */\nexport const ConversationsRoot: FC<ConversationsRootProps> = (props) => {\n const {\n registerNavItems,\n registerFooterItems,\n basePath = \"/\",\n workspaceId,\n } = props;\n const tr = useTr();\n\n useEffect(() => {\n if (!registerNavItems) return;\n\n const archivedPath = `${joinPath(basePath, \"list\")}?filter=archived`;\n\n const conversationsNavItem: NavItem = {\n id: \"conversations\",\n label: tr(\"conversations.title\", \"Conversations\"),\n path: basePath,\n icon: <MessageCircle size={20} />,\n order: 10,\n group: \"conversations\",\n children: [\n {\n id: \"conversations-inbox\",\n label: tr(\"conversations.inbox\", \"Inbox\"),\n path: basePath,\n icon: <MessageCircle size={16} />,\n order: 10,\n },\n {\n id: \"conversations-list\",\n label: tr(\"conversations.allConversations\", \"All conversations\"),\n path: joinPath(basePath, \"list\"),\n icon: <MessageCircle size={16} />,\n order: 20,\n },\n {\n id: \"conversations-new\",\n label: tr(\"conversations.newConversation\", \"New conversation\"),\n path:
|
|
1
|
+
{"version":3,"file":"ConversationsRoot.js","names":[],"sources":["../../src/conversations/ConversationsRoot.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { ErrorBoundary } from \"@burdenoff/fe-libs/shared/components\";\nimport { FeatureFlagProvider } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { MessageCircle, Plus, Settings } from \"lucide-react\";\nimport { ConversationsProvider } from \"./providers/ConversationsProvider\";\nimport type { NavItem, ConversationsRootProps } from \"./types\";\nimport { joinPath } from \"./utils/navigation\";\nimport { useTr } from \"./utils/i18n\";\nimport { ConversationsUserRoutes } from \"./ConversationsUserRoutes\";\n\n/**\n * Root component for the Conversations microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { ConversationsRoot, type ConversationsRootProps } from '@burdenoff/micro-fe/conversations';\n *\n * function App() {\n * const props: ConversationsRootProps = {\n * basePath: '/conversations',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com', firstName: 'John', lastName: 'Doe' },\n * workspaceId: 'ws-123',\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * defaultView: 'inbox',\n * onConversationSelect: (conversation) => console.log('Conversation selected:', conversation),\n * registerNavItems: (items) => { return () => {}; },\n * };\n *\n * return <ConversationsRoot {...props} />;\n * }\n * ```\n */\nexport const ConversationsRoot: FC<ConversationsRootProps> = (props) => {\n const {\n registerNavItems,\n registerFooterItems,\n basePath = \"/\",\n workspaceId,\n } = props;\n const tr = useTr();\n\n useEffect(() => {\n if (!registerNavItems) return;\n\n const archivedPath = `${joinPath(basePath, \"list\")}?filter=archived`;\n\n const conversationsNavItem: NavItem = {\n id: \"conversations\",\n label: tr(\"conversations.title\", \"Conversations\"),\n path: basePath,\n icon: <MessageCircle size={20} />,\n order: 10,\n group: \"conversations\",\n children: [\n {\n id: \"conversations-inbox\",\n label: tr(\"conversations.inbox\", \"Inbox\"),\n path: basePath,\n icon: <MessageCircle size={16} />,\n order: 10,\n },\n {\n id: \"conversations-list\",\n label: tr(\"conversations.allConversations\", \"All conversations\"),\n path: joinPath(basePath, \"list\"),\n icon: <MessageCircle size={16} />,\n order: 20,\n },\n {\n id: \"conversations-new\",\n label: tr(\"conversations.newConversation\", \"New conversation\"),\n // Canonical UX is the in-place modal opened via `?new=true` on\n // the inbox landing page — no full-page nav, the user stays in\n // the conversations surface.\n path: `${basePath}?new=true`,\n icon: <Plus size={16} />,\n order: 30,\n },\n {\n id: \"conversations-archived\",\n label: tr(\"conversations.archived\", \"Archived\"),\n path: archivedPath,\n icon: <MessageCircle size={16} />,\n order: 40,\n },\n {\n id: \"conversations-settings\",\n label: tr(\"conversations.settings\", \"Settings\"),\n path: joinPath(basePath, \"settings\"),\n icon: <Settings size={16} />,\n order: 50,\n },\n ],\n };\n\n const cleanup = registerNavItems(conversationsNavItem);\n return cleanup;\n }, [registerNavItems, basePath, tr]);\n\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: \"conversations-status\",\n label: tr(\"conversations.conversationsReady\", \"Conversations Ready\"),\n type: \"status\" as const,\n order: 10,\n section: \"left\" as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, tr]);\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\" role=\"alert\">\n {tr(\n \"conversations.moduleLoadError\",\n \"Something went wrong loading this module. Please refresh the page.\",\n )}\n </div>\n }\n onError={(error, errorInfo) => {\n // SRE forwarding — see ConversationsUserRoot for the migration target.\n console.error(\n \"[microfe-conversations:legacy-root] error boundary caught\",\n {\n message: error.message,\n stack: error.stack,\n componentStack: errorInfo.componentStack,\n },\n );\n }}\n >\n <FeatureFlagProvider\n workspaceId={workspaceId ?? null}\n gateway=\"global\"\n defaultEnabled={true}\n >\n <ConversationsProvider {...props}>\n <ConversationsUserRoutes />\n </ConversationsProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":";;;;;;;;;;AAuCA,IAAa,KAAiD,MAAU;CACtE,IAAM,EACJ,qBACA,wBACA,cAAW,KACX,mBACE,GACE,IAAK,GAAO;AA6ElB,QA3EA,QAAgB;AACd,MAAI,CAAC,EAAkB;EAEvB,IAAM,IAAe,GAAG,EAAS,GAAU,OAAO,CAAC;AAoDnD,SADgB,EAjDsB;GACpC,IAAI;GACJ,OAAO,EAAG,uBAAuB,gBAAgB;GACjD,MAAM;GACN,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;GACjC,OAAO;GACP,OAAO;GACP,UAAU;IACR;KACE,IAAI;KACJ,OAAO,EAAG,uBAAuB,QAAQ;KACzC,MAAM;KACN,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;KACjC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,kCAAkC,oBAAoB;KAChE,MAAM,EAAS,GAAU,OAAO;KAChC,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;KACjC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,iCAAiC,mBAAmB;KAI9D,MAAM,GAAG,EAAS;KAClB,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;KACxB,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,0BAA0B,WAAW;KAC/C,MAAM;KACN,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;KACjC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,0BAA0B,WAAW;KAC/C,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;KAC5B,OAAO;KACR;IACF;GACF,CAEqD;IAErD;EAAC;EAAkB;EAAU;EAAG,CAAC,EAEpC,QAAgB;AACT,QAaL,QADgB,EAVI,CAClB;GACE,IAAI;GACJ,OAAO,EAAG,oCAAoC,sBAAsB;GACpE,MAAM;GACN,OAAO;GACP,SAAS;GACV,CACF,CAE+C;IAE/C,CAAC,GAAqB,EAAG,CAAC,EAG3B,kBAAC,GAAD;EACE,UACE,kBAAC,OAAD;GAAK,WAAU;GAAsC,MAAK;aACvD,EACC,iCACA,qEACD;GACG,CAAA;EAER,UAAU,GAAO,MAAc;AAE7B,WAAQ,MACN,6DACA;IACE,SAAS,EAAM;IACf,OAAO,EAAM;IACb,gBAAgB,EAAU;IAC3B,CACF;;YAGH,kBAAC,GAAD;GACE,aAAa,KAAe;GAC5B,SAAQ;GACR,gBAAgB;aAEhB,kBAAC,GAAD;IAAuB,GAAI;cACzB,kBAAC,GAAD,EAA2B,CAAA;IACL,CAAA;GACJ,CAAA;EACR,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationsUserRoot.js","names":[],"sources":["../../src/conversations/ConversationsUserRoot.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { ErrorBoundary } from \"@burdenoff/fe-libs/shared/components\";\nimport { FeatureFlagProvider } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { MessageCircle, Plus, Settings } from \"lucide-react\";\nimport { ConversationsProvider } from \"./providers/ConversationsProvider\";\nimport type { NavItem, ConversationsRootProps } from \"./types\";\nimport { joinPath } from \"./utils/navigation\";\nimport { useTr } from \"./utils/i18n\";\nimport { ConversationsUserRoutes } from \"./ConversationsUserRoutes\";\n\n/**\n * User-facing Root component for the Conversations microfrontend.\n *\n * Mounted by user app-shells (vibecontrols-app, bigconsole-app,\n * workspaces-app, …). Registers the user nav tree (Inbox, All\n * conversations, New conversation, Archived, Settings), sets the\n * provider variant to `\"user\"` internally (host-passed `variant`\n * values are ignored), and renders `ConversationsUserRoutes`.\n *\n * Admin governance surfaces live in `ConversationsAdminRoot` and\n * must not be reached through this Root.\n */\nexport const ConversationsUserRoot: FC<ConversationsRootProps> = (props) => {\n const {\n registerNavItems,\n registerFooterItems,\n basePath = \"/\",\n workspaceId,\n } = props;\n const tr = useTr();\n\n useEffect(() => {\n if (!registerNavItems) return;\n\n const archivedPath = `${joinPath(basePath, \"list\")}?filter=archived`;\n\n const conversationsNavItem: NavItem = {\n id: \"conversations\",\n label: tr(\"conversations.title\", \"Conversations\"),\n path: basePath,\n icon: <MessageCircle size={20} />,\n order: 10,\n group: \"conversations\",\n children: [\n {\n id: \"conversations-inbox\",\n label: tr(\"conversations.inbox\", \"Inbox\"),\n path: basePath,\n icon: <MessageCircle size={16} />,\n order: 10,\n },\n {\n id: \"conversations-list\",\n label: tr(\"conversations.allConversations\", \"All conversations\"),\n path: joinPath(basePath, \"list\"),\n icon: <MessageCircle size={16} />,\n order: 20,\n },\n {\n id: \"conversations-new\",\n label: tr(\"conversations.newConversation\", \"New conversation\"),\n path:
|
|
1
|
+
{"version":3,"file":"ConversationsUserRoot.js","names":[],"sources":["../../src/conversations/ConversationsUserRoot.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { ErrorBoundary } from \"@burdenoff/fe-libs/shared/components\";\nimport { FeatureFlagProvider } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { MessageCircle, Plus, Settings } from \"lucide-react\";\nimport { ConversationsProvider } from \"./providers/ConversationsProvider\";\nimport type { NavItem, ConversationsRootProps } from \"./types\";\nimport { joinPath } from \"./utils/navigation\";\nimport { useTr } from \"./utils/i18n\";\nimport { ConversationsUserRoutes } from \"./ConversationsUserRoutes\";\n\n/**\n * User-facing Root component for the Conversations microfrontend.\n *\n * Mounted by user app-shells (vibecontrols-app, bigconsole-app,\n * workspaces-app, …). Registers the user nav tree (Inbox, All\n * conversations, New conversation, Archived, Settings), sets the\n * provider variant to `\"user\"` internally (host-passed `variant`\n * values are ignored), and renders `ConversationsUserRoutes`.\n *\n * Admin governance surfaces live in `ConversationsAdminRoot` and\n * must not be reached through this Root.\n */\nexport const ConversationsUserRoot: FC<ConversationsRootProps> = (props) => {\n const {\n registerNavItems,\n registerFooterItems,\n basePath = \"/\",\n workspaceId,\n } = props;\n const tr = useTr();\n\n useEffect(() => {\n if (!registerNavItems) return;\n\n const archivedPath = `${joinPath(basePath, \"list\")}?filter=archived`;\n\n const conversationsNavItem: NavItem = {\n id: \"conversations\",\n label: tr(\"conversations.title\", \"Conversations\"),\n path: basePath,\n icon: <MessageCircle size={20} />,\n order: 10,\n group: \"conversations\",\n children: [\n {\n id: \"conversations-inbox\",\n label: tr(\"conversations.inbox\", \"Inbox\"),\n path: basePath,\n icon: <MessageCircle size={16} />,\n order: 10,\n },\n {\n id: \"conversations-list\",\n label: tr(\"conversations.allConversations\", \"All conversations\"),\n path: joinPath(basePath, \"list\"),\n icon: <MessageCircle size={16} />,\n order: 20,\n },\n {\n id: \"conversations-new\",\n label: tr(\"conversations.newConversation\", \"New conversation\"),\n // Canonical UX is the in-place modal opened via `?new=true` on\n // the inbox landing page — no full-page nav, the user stays in\n // the conversations surface.\n path: `${basePath}?new=true`,\n icon: <Plus size={16} />,\n order: 30,\n },\n {\n id: \"conversations-archived\",\n label: tr(\"conversations.archived\", \"Archived\"),\n path: archivedPath,\n icon: <MessageCircle size={16} />,\n order: 40,\n },\n {\n id: \"conversations-settings\",\n label: tr(\"conversations.settings\", \"Settings\"),\n path: joinPath(basePath, \"settings\"),\n icon: <Settings size={16} />,\n order: 50,\n },\n ],\n };\n\n const cleanup = registerNavItems(conversationsNavItem);\n return cleanup;\n }, [registerNavItems, basePath, tr]);\n\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: \"conversations-status\",\n label: tr(\"conversations.conversationsReady\", \"Conversations Ready\"),\n type: \"status\" as const,\n order: 10,\n section: \"left\" as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, tr]);\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\" role=\"alert\">\n {tr(\n \"conversations.moduleLoadError\",\n \"Something went wrong loading this module. Please refresh the page.\",\n )}\n </div>\n }\n onError={(error, errorInfo) => {\n // SRE forwarding: log to the host's structured logger.\n // Per spec 47 the MFE does NOT call Rybbit / OTel directly — once the\n // shell event bus exposes a `conversations.error` event name in the\n // BurdenoffEventName union, swap the console.error here to\n // `bus.emit('conversations.error', { ... })`.\n // Tracker: ~/products/dev/stability/vibecontrols-conversations-stability.md Phase 3.7.\n console.error(\"[microfe-conversations] error boundary caught\", {\n message: error.message,\n stack: error.stack,\n componentStack: errorInfo.componentStack,\n });\n }}\n >\n <FeatureFlagProvider\n workspaceId={workspaceId ?? null}\n gateway=\"global\"\n defaultEnabled={true}\n >\n <ConversationsProvider {...props} variant=\"user\">\n <ConversationsUserRoutes />\n </ConversationsProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":";;;;;;;;;;AAuBA,IAAa,KAAqD,MAAU;CAC1E,IAAM,EACJ,qBACA,wBACA,cAAW,KACX,mBACE,GACE,IAAK,GAAO;AA6ElB,QA3EA,QAAgB;AACd,MAAI,CAAC,EAAkB;EAEvB,IAAM,IAAe,GAAG,EAAS,GAAU,OAAO,CAAC;AAoDnD,SADgB,EAjDsB;GACpC,IAAI;GACJ,OAAO,EAAG,uBAAuB,gBAAgB;GACjD,MAAM;GACN,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;GACjC,OAAO;GACP,OAAO;GACP,UAAU;IACR;KACE,IAAI;KACJ,OAAO,EAAG,uBAAuB,QAAQ;KACzC,MAAM;KACN,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;KACjC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,kCAAkC,oBAAoB;KAChE,MAAM,EAAS,GAAU,OAAO;KAChC,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;KACjC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,iCAAiC,mBAAmB;KAI9D,MAAM,GAAG,EAAS;KAClB,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;KACxB,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,0BAA0B,WAAW;KAC/C,MAAM;KACN,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;KACjC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAG,0BAA0B,WAAW;KAC/C,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;KAC5B,OAAO;KACR;IACF;GACF,CAEqD;IAErD;EAAC;EAAkB;EAAU;EAAG,CAAC,EAEpC,QAAgB;AACT,QAaL,QADgB,EAVI,CAClB;GACE,IAAI;GACJ,OAAO,EAAG,oCAAoC,sBAAsB;GACpE,MAAM;GACN,OAAO;GACP,SAAS;GACV,CACF,CAE+C;IAE/C,CAAC,GAAqB,EAAG,CAAC,EAG3B,kBAAC,GAAD;EACE,UACE,kBAAC,OAAD;GAAK,WAAU;GAAsC,MAAK;aACvD,EACC,iCACA,qEACD;GACG,CAAA;EAER,UAAU,GAAO,MAAc;AAO7B,WAAQ,MAAM,iDAAiD;IAC7D,SAAS,EAAM;IACf,OAAO,EAAM;IACb,gBAAgB,EAAU;IAC3B,CAAC;;YAGJ,kBAAC,GAAD;GACE,aAAa,KAAe;GAC5B,SAAQ;GACR,gBAAgB;aAEhB,kBAAC,GAAD;IAAuB,GAAI;IAAO,SAAQ;cACxC,kBAAC,GAAD,EAA2B,CAAA;IACL,CAAA;GACJ,CAAA;EACR,CAAA"}
|
|
@@ -3,8 +3,8 @@ import { LazyPageBoundary as t } from "./components/LazyPageBoundary.js";
|
|
|
3
3
|
import { Navigate as n, Route as r, Routes as i } from "react-router-dom";
|
|
4
4
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
5
5
|
//#region src/conversations/ConversationsUserRoutes.tsx
|
|
6
|
-
var s = e(() => import("./pages/ConversationListPage.js").then((e) => ({ default: e.ConversationListPage }))), c = e(() => import("./pages/ConversationsLandingPage.js").then((e) => ({ default: e.ConversationsLandingPage }))), l = e(() => import("./pages/
|
|
7
|
-
function
|
|
6
|
+
var s = e(() => import("./pages/ConversationListPage.js").then((e) => ({ default: e.ConversationListPage }))), c = e(() => import("./pages/ConversationsLandingPage.js").then((e) => ({ default: e.ConversationsLandingPage }))), l = e(() => import("./pages/NewConversationPage.js").then((e) => ({ default: e.NewConversationPage }))), u = e(() => import("./pages/ConversationsSettingsPage.js").then((e) => ({ default: e.ConversationsSettingsPage })));
|
|
7
|
+
function d() {
|
|
8
8
|
return /* @__PURE__ */ a(t, {
|
|
9
9
|
source: "user",
|
|
10
10
|
children: /* @__PURE__ */ o(i, { children: [
|
|
@@ -18,15 +18,15 @@ function f() {
|
|
|
18
18
|
}),
|
|
19
19
|
/* @__PURE__ */ a(r, {
|
|
20
20
|
path: "new",
|
|
21
|
-
element: /* @__PURE__ */ a(
|
|
21
|
+
element: /* @__PURE__ */ a(l, {})
|
|
22
22
|
}),
|
|
23
23
|
/* @__PURE__ */ a(r, {
|
|
24
24
|
path: "settings",
|
|
25
|
-
element: /* @__PURE__ */ a(
|
|
25
|
+
element: /* @__PURE__ */ a(u, {})
|
|
26
26
|
}),
|
|
27
27
|
/* @__PURE__ */ a(r, {
|
|
28
28
|
path: ":conversationId",
|
|
29
|
-
element: /* @__PURE__ */ a(
|
|
29
|
+
element: /* @__PURE__ */ a(c, {})
|
|
30
30
|
}),
|
|
31
31
|
/* @__PURE__ */ a(r, {
|
|
32
32
|
path: "*",
|
|
@@ -39,6 +39,6 @@ function f() {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
//#endregion
|
|
42
|
-
export {
|
|
42
|
+
export { d as ConversationsUserRoutes };
|
|
43
43
|
|
|
44
44
|
//# sourceMappingURL=ConversationsUserRoutes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationsUserRoutes.js","names":[],"sources":["../../src/conversations/ConversationsUserRoutes.tsx"],"sourcesContent":["import { Navigate, Route, Routes } from \"react-router-dom\";\n\nimport { LazyPageBoundary } from \"./components/LazyPageBoundary\";\nimport { lazyRetry } from \"./components/lazyRetry\";\nimport type { ConversationListPageProps } from \"./pages/ConversationListPage\";\n\nconst ConversationListPage = lazyRetry<ConversationListPageProps>(() =>\n import(\"./pages/ConversationListPage\").then((m) => ({\n default: m.ConversationListPage,\n })),\n);\nconst ConversationsLandingPage = lazyRetry<Record<string, never>>(() =>\n import(\"./pages/ConversationsLandingPage\").then((m) => ({\n default: m.ConversationsLandingPage,\n })),\n);\n\nconst
|
|
1
|
+
{"version":3,"file":"ConversationsUserRoutes.js","names":[],"sources":["../../src/conversations/ConversationsUserRoutes.tsx"],"sourcesContent":["import { Navigate, Route, Routes } from \"react-router-dom\";\n\nimport { LazyPageBoundary } from \"./components/LazyPageBoundary\";\nimport { lazyRetry } from \"./components/lazyRetry\";\nimport type { ConversationListPageProps } from \"./pages/ConversationListPage\";\n\nconst ConversationListPage = lazyRetry<ConversationListPageProps>(() =>\n import(\"./pages/ConversationListPage\").then((m) => ({\n default: m.ConversationListPage,\n })),\n);\nconst ConversationsLandingPage = lazyRetry<Record<string, never>>(() =>\n import(\"./pages/ConversationsLandingPage\").then((m) => ({\n default: m.ConversationsLandingPage,\n })),\n);\n\nconst NewConversationPage = lazyRetry<Record<string, never>>(() =>\n import(\"./pages/NewConversationPage\").then((m) => ({\n default: m.NewConversationPage,\n })),\n);\nconst ConversationsSettingsPage = lazyRetry<Record<string, never>>(() =>\n import(\"./pages/ConversationsSettingsPage\").then((m) => ({\n default: m.ConversationsSettingsPage,\n })),\n);\n\n/**\n * User-facing routes for the Conversations microfrontend.\n *\n * Routes (relative to basePath, e.g., /conversations):\n * - / -> Inbox (two-pane shell, no conversation selected)\n * - /:conversationId -> Inbox with conversation detail in right pane\n * - /list -> Legacy standalone list (kept for deep-linked callers)\n * - /new -> Legacy create route; backward-compat only.\n * Redirects to `..?new=true` so the inbox opens\n * the create dialog in-place. Kept registered\n * for deep links that may still exist in the\n * wild — the new canonical UX is the\n * `?new=true` query param on the landing page.\n * - /settings -> Personal notification settings\n *\n * Both `/` and `/:conversationId` mount `ConversationsLandingPage`. The page\n * reads `useParams().conversationId` and renders the `ConversationDetailPage`\n * in the right pane when set, otherwise the inbox empty state. This keeps\n * the two-pane experience consistent across deep links and sidebar clicks\n * — no full-page nav happens when a user picks a conversation on md+ screens.\n */\nexport function ConversationsUserRoutes() {\n return (\n <LazyPageBoundary source=\"user\">\n <Routes>\n <Route index element={<ConversationsLandingPage />} />\n <Route path=\"list\" element={<ConversationListPage />} />\n <Route path=\"new\" element={<NewConversationPage />} />\n <Route path=\"settings\" element={<ConversationsSettingsPage />} />\n <Route path=\":conversationId\" element={<ConversationsLandingPage />} />\n <Route path=\"*\" element={<Navigate to=\".\" replace />} />\n </Routes>\n </LazyPageBoundary>\n );\n}\n"],"mappings":";;;;;AAMA,IAAM,IAAuB,QAC3B,OAAO,mCAAgC,MAAM,OAAO,EAClD,SAAS,EAAE,sBACZ,EAAE,CACJ,EACK,IAA2B,QAC/B,OAAO,uCAAoC,MAAM,OAAO,EACtD,SAAS,EAAE,0BACZ,EAAE,CACJ,EAEK,IAAsB,QAC1B,OAAO,kCAA+B,MAAM,OAAO,EACjD,SAAS,EAAE,qBACZ,EAAE,CACJ,EACK,IAA4B,QAChC,OAAO,wCAAqC,MAAM,OAAO,EACvD,SAAS,EAAE,2BACZ,EAAE,CACJ;AAuBD,SAAgB,IAA0B;AACxC,QACE,kBAAC,GAAD;EAAkB,QAAO;YACvB,kBAAC,GAAD,EAAA,UAAA;GACE,kBAAC,GAAD;IAAO,OAAA;IAAM,SAAS,kBAAC,GAAD,EAA4B,CAAA;IAAI,CAAA;GACtD,kBAAC,GAAD;IAAO,MAAK;IAAO,SAAS,kBAAC,GAAD,EAAwB,CAAA;IAAI,CAAA;GACxD,kBAAC,GAAD;IAAO,MAAK;IAAM,SAAS,kBAAC,GAAD,EAAuB,CAAA;IAAI,CAAA;GACtD,kBAAC,GAAD;IAAO,MAAK;IAAW,SAAS,kBAAC,GAAD,EAA6B,CAAA;IAAI,CAAA;GACjE,kBAAC,GAAD;IAAO,MAAK;IAAkB,SAAS,kBAAC,GAAD,EAA4B,CAAA;IAAI,CAAA;GACvE,kBAAC,GAAD;IAAO,MAAK;IAAI,SAAS,kBAAC,GAAD;KAAU,IAAG;KAAI,SAAA;KAAU,CAAA;IAAI,CAAA;GACjD,EAAA,CAAA;EACQ,CAAA"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { useConversationActions as t } from "../hooks/useConversationQueries.js";
|
|
3
|
+
import { memo as n, useState as r } from "react";
|
|
4
|
+
import { ArchiveIcon as i, ArchiveRestore as a, Bell as o, BellOff as s, Bot as c, Hash as l, HeadphonesIcon as u, LogOut as d, Megaphone as f, MessageCircle as p, MoreHorizontal as m, Pin as h, PinOff as g, Users as _, VolumeX as v } from "lucide-react";
|
|
5
|
+
import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
6
|
+
import { useI18n as S } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
7
|
+
import { DropdownMenu as C, DropdownMenuContent as w, DropdownMenuItem as T, DropdownMenuSeparator as E, DropdownMenuTrigger as D } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
//#region src/conversations/components/ConversationRow.tsx
|
|
9
|
+
function O({ type: e, size: t = 16, className: n }) {
|
|
10
|
+
let r = {
|
|
11
|
+
size: t,
|
|
12
|
+
className: n
|
|
13
|
+
};
|
|
14
|
+
switch (e) {
|
|
15
|
+
case "DIRECT": return /* @__PURE__ */ b(p, { ...r });
|
|
16
|
+
case "GROUP": return /* @__PURE__ */ b(_, { ...r });
|
|
17
|
+
case "CHANNEL": return /* @__PURE__ */ b(l, { ...r });
|
|
18
|
+
case "BROADCAST":
|
|
19
|
+
case "ANNOUNCEMENT": return /* @__PURE__ */ b(f, { ...r });
|
|
20
|
+
case "BOT": return /* @__PURE__ */ b(c, { ...r });
|
|
21
|
+
case "SUPPORT": return /* @__PURE__ */ b(u, { ...r });
|
|
22
|
+
default: return /* @__PURE__ */ b(p, { ...r });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function k(e, t) {
|
|
26
|
+
let n = /* @__PURE__ */ new Date(), r = new Date(e), i = n.getTime() - r.getTime(), a = Math.floor(i / 1e3 / 60), o = Math.floor(a / 60), s = Math.floor(o / 24);
|
|
27
|
+
return a < 1 ? t.now : a < 60 ? `${a}m` : o < 24 ? `${o}h` : s === 1 ? t.yesterday : s < 7 ? `${s}d` : r.toLocaleDateString(void 0, {
|
|
28
|
+
month: "short",
|
|
29
|
+
day: "numeric"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function A(e) {
|
|
33
|
+
return e.split(" ").slice(0, 2).map((e) => e[0]).join("").toUpperCase();
|
|
34
|
+
}
|
|
35
|
+
var j = n(function({ conversation: n, isActive: c = !1, onClick: u, onLeave: f }) {
|
|
36
|
+
let { title: p, type: j, state: M, lastMessage: N, unreadCount: P, isPinned: F, isMuted: I, memberCount: L, updatedAt: R, avatar: z } = n, B = P > 0, V = M === "ARCHIVED", { t: H } = S(), U = (e, t) => {
|
|
37
|
+
let n = H(e);
|
|
38
|
+
return n === e ? t : n;
|
|
39
|
+
}, W = {
|
|
40
|
+
now: U("conversations.now", "now"),
|
|
41
|
+
yesterday: U("conversations.yesterday", "Yesterday")
|
|
42
|
+
}, [G, K] = r(!1), { archiveConversation: q, unarchiveConversation: J, pinConversation: Y, unpinConversation: X, muteConversation: Z, unmuteConversation: Q } = t(), $ = (e) => {
|
|
43
|
+
e.stopPropagation();
|
|
44
|
+
};
|
|
45
|
+
return /* @__PURE__ */ x("div", {
|
|
46
|
+
"data-tour": "conversation-row",
|
|
47
|
+
className: e("group relative flex w-full min-w-0 items-start gap-3 rounded-md px-3 py-2.5 transition-colors", "cursor-pointer select-none", c ? "bg-bg-sunken border-l-2 border-l-action-primary-bg" : "border-l-2 border-l-transparent hover:bg-action-ghost-bg-hover", B && !c && "bg-bg-elevated/40"),
|
|
48
|
+
role: "button",
|
|
49
|
+
tabIndex: 0,
|
|
50
|
+
onClick: () => u?.(n),
|
|
51
|
+
onKeyDown: (e) => {
|
|
52
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), u?.(n));
|
|
53
|
+
},
|
|
54
|
+
"aria-label": U("conversations.openConversation", "Open conversation") + ": " + p,
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ b("div", {
|
|
57
|
+
className: e("flex-shrink-0 flex h-9 w-9 items-center justify-center rounded-full text-xs font-semibold overflow-hidden", j === "DIRECT" && "bg-status-info-bg-subtle text-status-info-text", j === "CHANNEL" && "bg-status-success-bg-subtle text-status-success-text", (j === "GROUP" || j === "BROADCAST" || j === "ANNOUNCEMENT" || j === "BOT" || j === "SUPPORT") && "bg-bg-sunken text-text-primary"),
|
|
58
|
+
"aria-hidden": "true",
|
|
59
|
+
children: z ? /* @__PURE__ */ b("img", {
|
|
60
|
+
src: z,
|
|
61
|
+
alt: "",
|
|
62
|
+
className: "h-full w-full object-cover"
|
|
63
|
+
}) : j === "DIRECT" ? A(p) : j === "CHANNEL" ? /* @__PURE__ */ b(l, { size: 16 }) : /* @__PURE__ */ b(O, {
|
|
64
|
+
type: j,
|
|
65
|
+
size: 16
|
|
66
|
+
})
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ x("div", {
|
|
69
|
+
className: "flex min-w-0 flex-1 flex-col gap-0.5",
|
|
70
|
+
children: [/* @__PURE__ */ x("div", {
|
|
71
|
+
className: "flex items-center justify-between gap-2",
|
|
72
|
+
children: [/* @__PURE__ */ x("div", {
|
|
73
|
+
className: "flex min-w-0 items-center gap-1.5",
|
|
74
|
+
children: [
|
|
75
|
+
/* @__PURE__ */ b("span", {
|
|
76
|
+
className: e("truncate text-sm", B ? "font-semibold text-text-primary" : "font-medium text-text-primary"),
|
|
77
|
+
children: p
|
|
78
|
+
}),
|
|
79
|
+
F && /* @__PURE__ */ b(h, {
|
|
80
|
+
size: 11,
|
|
81
|
+
className: "flex-shrink-0 text-text-secondary",
|
|
82
|
+
"aria-label": U("conversations.pinnedLabel", "Pinned")
|
|
83
|
+
}),
|
|
84
|
+
I && /* @__PURE__ */ b(v, {
|
|
85
|
+
size: 11,
|
|
86
|
+
className: "flex-shrink-0 text-text-secondary",
|
|
87
|
+
"aria-label": U("conversations.mutedLabel", "Muted")
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
}), /* @__PURE__ */ b("span", {
|
|
91
|
+
className: e("flex-shrink-0 text-[11px]", B ? "font-medium text-action-primary-bg" : "text-text-secondary"),
|
|
92
|
+
children: k(N?.createdAt ?? R, W)
|
|
93
|
+
})]
|
|
94
|
+
}), /* @__PURE__ */ x("div", {
|
|
95
|
+
className: "flex items-center justify-between gap-2",
|
|
96
|
+
children: [/* @__PURE__ */ b("p", {
|
|
97
|
+
className: e("min-w-0 truncate break-all text-xs", B ? "text-text-primary/80" : "text-text-secondary"),
|
|
98
|
+
children: N ? /* @__PURE__ */ x(y, { children: [N.sender.name && /* @__PURE__ */ x("span", {
|
|
99
|
+
className: "font-medium",
|
|
100
|
+
children: [N.sender.name, ": "]
|
|
101
|
+
}), N.content || U("conversations.sentAttachment", "Sent an attachment")] }) : /* @__PURE__ */ b("span", {
|
|
102
|
+
className: "italic",
|
|
103
|
+
children: U("conversations.noMessages", "No messages yet")
|
|
104
|
+
})
|
|
105
|
+
}), /* @__PURE__ */ x("div", {
|
|
106
|
+
className: "flex flex-shrink-0 items-center gap-1.5",
|
|
107
|
+
children: [j !== "DIRECT" && /* @__PURE__ */ x("span", {
|
|
108
|
+
className: "inline-flex items-center gap-0.5 text-[10px] text-text-secondary",
|
|
109
|
+
"aria-label": `${L} ${U("conversations.membersLabel", "members")}`,
|
|
110
|
+
children: [/* @__PURE__ */ b(_, {
|
|
111
|
+
size: 10,
|
|
112
|
+
"aria-hidden": "true"
|
|
113
|
+
}), L]
|
|
114
|
+
}), B && /* @__PURE__ */ b("span", {
|
|
115
|
+
className: "inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-action-primary-bg px-1 text-[10px] font-bold text-action-primary-text",
|
|
116
|
+
"aria-label": `${P} ${U("conversations.unreadCountLabel", "unread")}`,
|
|
117
|
+
children: P > 99 ? "99+" : P
|
|
118
|
+
})]
|
|
119
|
+
})]
|
|
120
|
+
})]
|
|
121
|
+
}),
|
|
122
|
+
/* @__PURE__ */ x(C, {
|
|
123
|
+
open: G,
|
|
124
|
+
onOpenChange: K,
|
|
125
|
+
children: [/* @__PURE__ */ b(D, {
|
|
126
|
+
asChild: !0,
|
|
127
|
+
children: /* @__PURE__ */ b("button", {
|
|
128
|
+
type: "button",
|
|
129
|
+
onClick: $,
|
|
130
|
+
onKeyDown: $,
|
|
131
|
+
"aria-label": U("conversations.rowMenuLabel", "Conversation actions"),
|
|
132
|
+
className: e("absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-md transition-opacity", "text-text-secondary hover:bg-action-ghost-bg-hover hover:text-text-primary", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-border", G ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"),
|
|
133
|
+
children: /* @__PURE__ */ b(m, {
|
|
134
|
+
size: 14,
|
|
135
|
+
"aria-hidden": "true"
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
}), /* @__PURE__ */ x(w, {
|
|
139
|
+
align: "end",
|
|
140
|
+
className: "min-w-[180px]",
|
|
141
|
+
onClick: $,
|
|
142
|
+
children: [
|
|
143
|
+
/* @__PURE__ */ b(T, {
|
|
144
|
+
onSelect: () => {
|
|
145
|
+
F ? X(n.id) : Y(n.id);
|
|
146
|
+
},
|
|
147
|
+
children: F ? /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(g, { size: 14 }), U("conversations.unpin", "Unpin")] }) : /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(h, { size: 14 }), U("conversations.pin", "Pin")] })
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ b(T, {
|
|
150
|
+
onSelect: () => {
|
|
151
|
+
I ? Q(n.id) : Z(n.id);
|
|
152
|
+
},
|
|
153
|
+
children: I ? /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(o, { size: 14 }), U("conversations.unmute", "Unmute")] }) : /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(s, { size: 14 }), U("conversations.mute", "Mute")] })
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ b(E, {}),
|
|
156
|
+
/* @__PURE__ */ b(T, {
|
|
157
|
+
onSelect: () => {
|
|
158
|
+
V ? J(n.id) : q(n.id);
|
|
159
|
+
},
|
|
160
|
+
children: V ? /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(a, { size: 14 }), U("conversations.unarchive", "Unarchive")] }) : /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(i, { size: 14 }), U("conversations.archive", "Archive")] })
|
|
161
|
+
}),
|
|
162
|
+
(j === "GROUP" || j === "CHANNEL") && f && /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(E, {}), /* @__PURE__ */ x(T, {
|
|
163
|
+
onSelect: () => f(n),
|
|
164
|
+
className: "text-status-error-text focus:text-status-error-text",
|
|
165
|
+
children: [/* @__PURE__ */ b(d, { size: 14 }), U("conversations.leave", "Leave")]
|
|
166
|
+
})] })
|
|
167
|
+
]
|
|
168
|
+
})]
|
|
169
|
+
})
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
//#endregion
|
|
174
|
+
export { j as ConversationRow };
|
|
175
|
+
|
|
176
|
+
//# sourceMappingURL=ConversationRow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationRow.js","names":[],"sources":["../../../src/conversations/components/ConversationRow.tsx"],"sourcesContent":["import { memo, useState } from \"react\";\nimport {\n ArchiveIcon,\n ArchiveRestore,\n Bell,\n BellOff,\n Bot,\n Hash,\n HeadphonesIcon,\n LogOut,\n Megaphone,\n MessageCircle,\n MoreHorizontal,\n Pin,\n PinOff,\n Users,\n VolumeX,\n} from \"lucide-react\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { cn } from \"../utils/cn\";\nimport { useConversationActions } from \"../hooks/useConversationQueries\";\nimport type { Conversation } from \"../types\";\n\nexport interface ConversationRowProps {\n conversation: Conversation;\n isActive?: boolean;\n onClick?: (conversation: Conversation) => void;\n /** Optional leave handler — only the host has the membership mutation. */\n onLeave?: (conversation: Conversation) => void;\n}\n\nfunction ConversationTypeIcon({\n type,\n size = 16,\n className,\n}: {\n type: Conversation[\"type\"];\n size?: number;\n className?: string;\n}) {\n const props = { size, className };\n switch (type) {\n case \"DIRECT\":\n return <MessageCircle {...props} />;\n case \"GROUP\":\n return <Users {...props} />;\n case \"CHANNEL\":\n return <Hash {...props} />;\n case \"BROADCAST\":\n case \"ANNOUNCEMENT\":\n return <Megaphone {...props} />;\n case \"BOT\":\n return <Bot {...props} />;\n case \"SUPPORT\":\n return <HeadphonesIcon {...props} />;\n default:\n return <MessageCircle {...props} />;\n }\n}\n\nfunction formatRelativeTime(\n dateString: string,\n labels: { now: string; yesterday: string },\n): string {\n const now = new Date();\n const date = new Date(dateString);\n const diffMs = now.getTime() - date.getTime();\n const diffMin = Math.floor(diffMs / 1000 / 60);\n const diffHr = Math.floor(diffMin / 60);\n const diffDay = Math.floor(diffHr / 24);\n\n if (diffMin < 1) return labels.now;\n if (diffMin < 60) return `${diffMin}m`;\n if (diffHr < 24) return `${diffHr}h`;\n if (diffDay === 1) return labels.yesterday;\n if (diffDay < 7) return `${diffDay}d`;\n return date.toLocaleDateString(undefined, { month: \"short\", day: \"numeric\" });\n}\n\nfunction getInitials(title: string): string {\n return title\n .split(\" \")\n .slice(0, 2)\n .map((word) => word[0])\n .join(\"\")\n .toUpperCase();\n}\n\n/**\n * ConversationRow — inbox-style list row.\n *\n * Visual hierarchy:\n * [avatar] Title · 2h\n * last message preview (badge)\n *\n * On hover the kebab (3-dots) menu reveals so the user can pin/mute/archive\n * without opening the conversation first. Wires straight into\n * `useConversationActions` for archive/unarchive/pin/mute.\n */\nexport const ConversationRow = memo(function ConversationRow({\n conversation,\n isActive = false,\n onClick,\n onLeave,\n}: ConversationRowProps) {\n const {\n title,\n type,\n state,\n lastMessage,\n unreadCount,\n isPinned,\n isMuted,\n memberCount,\n updatedAt,\n avatar,\n } = conversation;\n\n const hasUnread = unreadCount > 0;\n const isArchived = state === \"ARCHIVED\";\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const timeLabels = {\n now: tr(\"conversations.now\", \"now\"),\n yesterday: tr(\"conversations.yesterday\", \"Yesterday\"),\n };\n\n // Track menu open so the kebab stays visible even when the cursor leaves\n // the row (otherwise the radix portal closes on every hover-out).\n const [menuOpen, setMenuOpen] = useState(false);\n\n const {\n archiveConversation,\n unarchiveConversation,\n pinConversation,\n unpinConversation,\n muteConversation,\n unmuteConversation,\n } = useConversationActions();\n\n // Stop click bubbling from the kebab into the row's click handler — opening\n // the menu must not also open the conversation.\n const stop = (event: React.MouseEvent | React.KeyboardEvent) => {\n event.stopPropagation();\n };\n\n return (\n <div\n data-tour=\"conversation-row\"\n className={cn(\n \"group relative flex w-full min-w-0 items-start gap-3 rounded-md px-3 py-2.5 transition-colors\",\n \"cursor-pointer select-none\",\n isActive\n ? \"bg-bg-sunken border-l-2 border-l-action-primary-bg\"\n : \"border-l-2 border-l-transparent hover:bg-action-ghost-bg-hover\",\n hasUnread && !isActive && \"bg-bg-elevated/40\",\n )}\n role=\"button\"\n tabIndex={0}\n onClick={() => onClick?.(conversation)}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n onClick?.(conversation);\n }\n }}\n aria-label={\n tr(\"conversations.openConversation\", \"Open conversation\") + \": \" + title\n }\n >\n {/* Avatar / type icon */}\n <div\n className={cn(\n \"flex-shrink-0 flex h-9 w-9 items-center justify-center rounded-full text-xs font-semibold overflow-hidden\",\n type === \"DIRECT\" && \"bg-status-info-bg-subtle text-status-info-text\",\n type === \"CHANNEL\" &&\n \"bg-status-success-bg-subtle text-status-success-text\",\n (type === \"GROUP\" ||\n type === \"BROADCAST\" ||\n type === \"ANNOUNCEMENT\" ||\n type === \"BOT\" ||\n type === \"SUPPORT\") &&\n \"bg-bg-sunken text-text-primary\",\n )}\n aria-hidden=\"true\"\n >\n {avatar ? (\n <img src={avatar} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : type === \"DIRECT\" ? (\n getInitials(title)\n ) : type === \"CHANNEL\" ? (\n <Hash size={16} />\n ) : (\n <ConversationTypeIcon type={type} size={16} />\n )}\n </div>\n\n {/* Body */}\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span\n className={cn(\n \"truncate text-sm\",\n hasUnread\n ? \"font-semibold text-text-primary\"\n : \"font-medium text-text-primary\",\n )}\n >\n {title}\n </span>\n {isPinned && (\n <Pin\n size={11}\n className=\"flex-shrink-0 text-text-secondary\"\n aria-label={tr(\"conversations.pinnedLabel\", \"Pinned\")}\n />\n )}\n {isMuted && (\n <VolumeX\n size={11}\n className=\"flex-shrink-0 text-text-secondary\"\n aria-label={tr(\"conversations.mutedLabel\", \"Muted\")}\n />\n )}\n </div>\n\n <span\n className={cn(\n \"flex-shrink-0 text-[11px]\",\n hasUnread\n ? \"font-medium text-action-primary-bg\"\n : \"text-text-secondary\",\n )}\n >\n {formatRelativeTime(\n lastMessage?.createdAt ?? updatedAt,\n timeLabels,\n )}\n </span>\n </div>\n\n <div className=\"flex items-center justify-between gap-2\">\n <p\n className={cn(\n \"min-w-0 truncate break-all text-xs\",\n hasUnread ? \"text-text-primary/80\" : \"text-text-secondary\",\n )}\n >\n {lastMessage ? (\n <>\n {lastMessage.sender.name && (\n <span className=\"font-medium\">\n {lastMessage.sender.name}\n {\": \"}\n </span>\n )}\n {lastMessage.content ||\n tr(\"conversations.sentAttachment\", \"Sent an attachment\")}\n </>\n ) : (\n <span className=\"italic\">\n {tr(\"conversations.noMessages\", \"No messages yet\")}\n </span>\n )}\n </p>\n\n <div className=\"flex flex-shrink-0 items-center gap-1.5\">\n {type !== \"DIRECT\" && (\n <span\n className=\"inline-flex items-center gap-0.5 text-[10px] text-text-secondary\"\n aria-label={`${memberCount} ${tr(\"conversations.membersLabel\", \"members\")}`}\n >\n <Users size={10} aria-hidden=\"true\" />\n {memberCount}\n </span>\n )}\n {hasUnread && (\n <span\n className=\"inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-action-primary-bg px-1 text-[10px] font-bold text-action-primary-text\"\n aria-label={`${unreadCount} ${tr(\"conversations.unreadCountLabel\", \"unread\")}`}\n >\n {unreadCount > 99 ? \"99+\" : unreadCount}\n </span>\n )}\n </div>\n </div>\n </div>\n\n {/* Kebab menu — hidden until hover or menu open */}\n <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n onClick={stop}\n onKeyDown={stop}\n aria-label={tr(\n \"conversations.rowMenuLabel\",\n \"Conversation actions\",\n )}\n className={cn(\n \"absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-md transition-opacity\",\n \"text-text-secondary hover:bg-action-ghost-bg-hover hover:text-text-primary\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-border\",\n menuOpen\n ? \"opacity-100\"\n : \"opacity-0 group-hover:opacity-100 group-focus-within:opacity-100\",\n )}\n >\n <MoreHorizontal size={14} aria-hidden=\"true\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"min-w-[180px]\"\n onClick={stop}\n >\n <DropdownMenuItem\n onSelect={() => {\n void (isPinned\n ? unpinConversation(conversation.id)\n : pinConversation(conversation.id));\n }}\n >\n {isPinned ? (\n <>\n <PinOff size={14} />\n {tr(\"conversations.unpin\", \"Unpin\")}\n </>\n ) : (\n <>\n <Pin size={14} />\n {tr(\"conversations.pin\", \"Pin\")}\n </>\n )}\n </DropdownMenuItem>\n <DropdownMenuItem\n onSelect={() => {\n void (isMuted\n ? unmuteConversation(conversation.id)\n : muteConversation(conversation.id));\n }}\n >\n {isMuted ? (\n <>\n <Bell size={14} />\n {tr(\"conversations.unmute\", \"Unmute\")}\n </>\n ) : (\n <>\n <BellOff size={14} />\n {tr(\"conversations.mute\", \"Mute\")}\n </>\n )}\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onSelect={() => {\n void (isArchived\n ? unarchiveConversation(conversation.id)\n : archiveConversation(conversation.id));\n }}\n >\n {isArchived ? (\n <>\n <ArchiveRestore size={14} />\n {tr(\"conversations.unarchive\", \"Unarchive\")}\n </>\n ) : (\n <>\n <ArchiveIcon size={14} />\n {tr(\"conversations.archive\", \"Archive\")}\n </>\n )}\n </DropdownMenuItem>\n {(type === \"GROUP\" || type === \"CHANNEL\") && onLeave && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onSelect={() => onLeave(conversation)}\n className=\"text-status-error-text focus:text-status-error-text\"\n >\n <LogOut size={14} />\n {tr(\"conversations.leave\", \"Leave\")}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n});\n"],"mappings":";;;;;;;;AAsCA,SAAS,EAAqB,EAC5B,SACA,UAAO,IACP,gBAKC;CACD,IAAM,IAAQ;EAAE;EAAM;EAAW;AACjC,SAAQ,GAAR;EACE,KAAK,SACH,QAAO,kBAAC,GAAD,EAAe,GAAI,GAAS,CAAA;EACrC,KAAK,QACH,QAAO,kBAAC,GAAD,EAAO,GAAI,GAAS,CAAA;EAC7B,KAAK,UACH,QAAO,kBAAC,GAAD,EAAM,GAAI,GAAS,CAAA;EAC5B,KAAK;EACL,KAAK,eACH,QAAO,kBAAC,GAAD,EAAW,GAAI,GAAS,CAAA;EACjC,KAAK,MACH,QAAO,kBAAC,GAAD,EAAK,GAAI,GAAS,CAAA;EAC3B,KAAK,UACH,QAAO,kBAAC,GAAD,EAAgB,GAAI,GAAS,CAAA;EACtC,QACE,QAAO,kBAAC,GAAD,EAAe,GAAI,GAAS,CAAA;;;AAIzC,SAAS,EACP,GACA,GACQ;CACR,IAAM,oBAAM,IAAI,MAAM,EAChB,IAAO,IAAI,KAAK,EAAW,EAC3B,IAAS,EAAI,SAAS,GAAG,EAAK,SAAS,EACvC,IAAU,KAAK,MAAM,IAAS,MAAO,GAAG,EACxC,IAAS,KAAK,MAAM,IAAU,GAAG,EACjC,IAAU,KAAK,MAAM,IAAS,GAAG;AAOvC,QALI,IAAU,IAAU,EAAO,MAC3B,IAAU,KAAW,GAAG,EAAQ,KAChC,IAAS,KAAW,GAAG,EAAO,KAC9B,MAAY,IAAU,EAAO,YAC7B,IAAU,IAAU,GAAG,EAAQ,KAC5B,EAAK,mBAAmB,KAAA,GAAW;EAAE,OAAO;EAAS,KAAK;EAAW,CAAC;;AAG/E,SAAS,EAAY,GAAuB;AAC1C,QAAO,EACJ,MAAM,IAAI,CACV,MAAM,GAAG,EAAE,CACX,KAAK,MAAS,EAAK,GAAG,CACtB,KAAK,GAAG,CACR,aAAa;;AAclB,IAAa,IAAkB,EAAK,SAAyB,EAC3D,iBACA,cAAW,IACX,YACA,cACuB;CACvB,IAAM,EACJ,UACA,SACA,UACA,gBACA,gBACA,aACA,YACA,gBACA,cACA,cACE,GAEE,IAAY,IAAc,GAC1B,IAAa,MAAU,YACvB,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAE9B,IAAa;EACjB,KAAK,EAAG,qBAAqB,MAAM;EACnC,WAAW,EAAG,2BAA2B,YAAY;EACtD,EAIK,CAAC,GAAU,KAAe,EAAS,GAAM,EAEzC,EACJ,wBACA,0BACA,oBACA,sBACA,qBACA,0BACE,GAAwB,EAItB,KAAQ,MAAkD;AAC9D,IAAM,iBAAiB;;AAGzB,QACE,kBAAC,OAAD;EACE,aAAU;EACV,WAAW,EACT,iGACA,8BACA,IACI,uDACA,kEACJ,KAAa,CAAC,KAAY,oBAC3B;EACD,MAAK;EACL,UAAU;EACV,eAAe,IAAU,EAAa;EACtC,YAAY,MAAU;AACpB,IAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,IAAU,EAAa;;EAG3B,cACE,EAAG,kCAAkC,oBAAoB,GAAG,OAAO;YApBvE;GAwBE,kBAAC,OAAD;IACE,WAAW,EACT,6GACA,MAAS,YAAY,kDACrB,MAAS,aACP,yDACD,MAAS,WACR,MAAS,eACT,MAAS,kBACT,MAAS,SACT,MAAS,cACT,iCACH;IACD,eAAY;cAEX,IACC,kBAAC,OAAD;KAAK,KAAK;KAAQ,KAAI;KAAG,WAAU;KAA+B,CAAA,GAChE,MAAS,WACX,EAAY,EAAM,GAChB,MAAS,YACX,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,GAElB,kBAAC,GAAD;KAA4B;KAAM,MAAM;KAAM,CAAA;IAE5C,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD;QACE,WAAW,EACT,oBACA,IACI,oCACA,gCACL;kBAEA;QACI,CAAA;OACN,KACC,kBAAC,GAAD;QACE,MAAM;QACN,WAAU;QACV,cAAY,EAAG,6BAA6B,SAAS;QACrD,CAAA;OAEH,KACC,kBAAC,GAAD;QACE,MAAM;QACN,WAAU;QACV,cAAY,EAAG,4BAA4B,QAAQ;QACnD,CAAA;OAEA;SAEN,kBAAC,QAAD;MACE,WAAW,EACT,6BACA,IACI,uCACA,sBACL;gBAEA,EACC,GAAa,aAAa,GAC1B,EACD;MACI,CAAA,CACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MACE,WAAW,EACT,sCACA,IAAY,yBAAyB,sBACtC;gBAEA,IACC,kBAAA,GAAA,EAAA,UAAA,CACG,EAAY,OAAO,QAClB,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACG,EAAY,OAAO,MACnB,KACI;UAER,EAAY,WACX,EAAG,gCAAgC,qBAAqB,CACzD,EAAA,CAAA,GAEH,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,4BAA4B,kBAAkB;OAC7C,CAAA;MAEP,CAAA,EAEJ,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,MAAS,YACR,kBAAC,QAAD;OACE,WAAU;OACV,cAAY,GAAG,EAAY,GAAG,EAAG,8BAA8B,UAAU;iBAF3E,CAIE,kBAAC,GAAD;QAAO,MAAM;QAAI,eAAY;QAAS,CAAA,EACrC,EACI;UAER,KACC,kBAAC,QAAD;OACE,WAAU;OACV,cAAY,GAAG,EAAY,GAAG,EAAG,kCAAkC,SAAS;iBAE3E,IAAc,KAAK,QAAQ;OACvB,CAAA,CAEL;QACF;OACF;;GAGN,kBAAC,GAAD;IAAc,MAAM;IAAU,cAAc;cAA5C,CACE,kBAAC,GAAD;KAAqB,SAAA;eACnB,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAW;MACX,cAAY,EACV,8BACA,uBACD;MACD,WAAW,EACT,iGACA,8EACA,4FACA,IACI,gBACA,mEACL;gBAED,kBAAC,GAAD;OAAgB,MAAM;OAAI,eAAY;OAAS,CAAA;MACxC,CAAA;KACW,CAAA,EACtB,kBAAC,GAAD;KACE,OAAM;KACN,WAAU;KACV,SAAS;eAHX;MAKE,kBAAC,GAAD;OACE,gBAAgB;AACR,YACF,EAAkB,EAAa,GAAG,GAClC,EAAgB,EAAa,GAAG;;iBAGrC,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACnB,EAAG,uBAAuB,QAAQ,CAClC,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAK,MAAM,IAAM,CAAA,EAChB,EAAG,qBAAqB,MAAM,CAC9B,EAAA,CAAA;OAEY,CAAA;MACnB,kBAAC,GAAD;OACE,gBAAgB;AACR,YACF,EAAmB,EAAa,GAAG,GACnC,EAAiB,EAAa,GAAG;;iBAGtC,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,wBAAwB,SAAS,CACpC,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA,EACpB,EAAG,sBAAsB,OAAO,CAChC,EAAA,CAAA;OAEY,CAAA;MACnB,kBAAC,GAAD,EAAyB,CAAA;MACzB,kBAAC,GAAD;OACE,gBAAgB;AACR,YACF,EAAsB,EAAa,GAAG,GACtC,EAAoB,EAAa,GAAG;;iBAGzC,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAgB,MAAM,IAAM,CAAA,EAC3B,EAAG,2BAA2B,YAAY,CAC1C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EACxB,EAAG,yBAAyB,UAAU,CACtC,EAAA,CAAA;OAEY,CAAA;OACjB,MAAS,WAAW,MAAS,cAAc,KAC3C,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAyB,CAAA,EACzB,kBAAC,GAAD;OACE,gBAAgB,EAAQ,EAAa;OACrC,WAAU;iBAFZ,CAIE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACnB,EAAG,uBAAuB,QAAQ,CAClB;SAClB,EAAA,CAAA;MAEe;OACT;;GACX;;EAER"}
|
|
@@ -1,27 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { EmptyState as t } from "@burdenoff/fe-libs/ui";
|
|
3
3
|
//#region src/conversations/components/EmptyState.tsx
|
|
4
|
-
function
|
|
5
|
-
return /* @__PURE__ */
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}),
|
|
12
|
-
/* @__PURE__ */ t("h3", {
|
|
13
|
-
className: "text-lg font-semibold text-foreground mb-1",
|
|
14
|
-
children: i
|
|
15
|
-
}),
|
|
16
|
-
a && /* @__PURE__ */ t("p", {
|
|
17
|
-
className: "text-sm text-muted-foreground max-w-sm mb-4",
|
|
18
|
-
children: a
|
|
19
|
-
}),
|
|
20
|
-
o
|
|
21
|
-
]
|
|
4
|
+
function n({ icon: n, title: r, description: i, action: a, className: o }) {
|
|
5
|
+
return /* @__PURE__ */ e(t, {
|
|
6
|
+
icon: n,
|
|
7
|
+
title: r,
|
|
8
|
+
description: i,
|
|
9
|
+
action: a,
|
|
10
|
+
className: o
|
|
22
11
|
});
|
|
23
12
|
}
|
|
24
13
|
//#endregion
|
|
25
|
-
export {
|
|
14
|
+
export { n as EmptyState };
|
|
26
15
|
|
|
27
16
|
//# sourceMappingURL=EmptyState.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/conversations/components/EmptyState.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/conversations/components/EmptyState.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { EmptyState as CanonicalEmptyState } from \"@burdenoff/fe-libs/ui\";\n\n/**\n * Local wrapper around `EmptyState` from `@burdenoff/fe-libs/ui`. Keeps the\n * existing microfe-conversations call sites stable while routing all\n * empty-state rendering through the canonical primitive.\n */\nexport function EmptyState({\n icon,\n title,\n description,\n action,\n className,\n}: {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n className?: string;\n}) {\n return (\n <CanonicalEmptyState\n icon={icon}\n title={title}\n description={description}\n action={action}\n className={className}\n />\n );\n}\n"],"mappings":";;;AAQA,SAAgB,EAAW,EACzB,SACA,UACA,gBACA,WACA,gBAOC;AACD,QACE,kBAAC,GAAD;EACQ;EACC;EACM;EACL;EACG;EACX,CAAA"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { useConversations as e } from "../providers/ConversationsProvider.js";
|
|
2
|
+
import { useTr as t } from "../utils/i18n.js";
|
|
3
|
+
import { cn as n } from "../utils/cn.js";
|
|
4
|
+
import { useConversationsStore as r } from "../store/conversationsStore.js";
|
|
5
|
+
import { useCallback as i, useMemo as a } from "react";
|
|
6
|
+
import { ArrowLeft as o, MessageSquarePlus as s, Search as c, X as l } from "lucide-react";
|
|
7
|
+
import { useLocation as u, useNavigate as d, useParams as f } from "react-router-dom";
|
|
8
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
9
|
+
import { Tabs as h } from "@burdenoff/fe-libs/chrome";
|
|
10
|
+
import { Button as g } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
//#region src/conversations/components/InboxLayout.tsx
|
|
12
|
+
function _({ activeTab: _, onTabChange: y, archivedEnabled: b = !0, searchQuery: x, onSearchChange: S, filterChips: C, listSlot: w, detailSlot: T, emptyDetailSlot: E }) {
|
|
13
|
+
let D = d(), O = t(), { basePath: k } = e(), { setActiveConversationId: A } = r(), j = f(), M = u(), N = !!j.conversationId && !!T, P = a(() => {
|
|
14
|
+
let e = [
|
|
15
|
+
{
|
|
16
|
+
id: "all",
|
|
17
|
+
label: O("conversations.tabs.all", "All")
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "direct",
|
|
21
|
+
label: O("conversations.tabs.direct", "Direct")
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "groups",
|
|
25
|
+
label: O("conversations.tabs.groups", "Groups")
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "channels",
|
|
29
|
+
label: O("conversations.tabs.channels", "Channels")
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
return b && e.push({
|
|
33
|
+
id: "archived",
|
|
34
|
+
label: O("conversations.tabs.archived", "Archived")
|
|
35
|
+
}), e;
|
|
36
|
+
}, [b, O]), F = i(() => {
|
|
37
|
+
let e = k || "/conversations", t = new URLSearchParams(M.search);
|
|
38
|
+
t.set("new", "true"), D(`${e}?${t.toString()}`);
|
|
39
|
+
}, [
|
|
40
|
+
k,
|
|
41
|
+
M.search,
|
|
42
|
+
D
|
|
43
|
+
]), I = i(() => {
|
|
44
|
+
A(null), D(`${k || "/conversations"}${M.search}`);
|
|
45
|
+
}, [
|
|
46
|
+
k,
|
|
47
|
+
M.search,
|
|
48
|
+
D,
|
|
49
|
+
A
|
|
50
|
+
]);
|
|
51
|
+
return /* @__PURE__ */ m("div", {
|
|
52
|
+
"data-tour": "conversations-inbox",
|
|
53
|
+
className: "flex h-full w-full min-h-0 min-w-0 overflow-hidden bg-bg-canvas",
|
|
54
|
+
children: [/* @__PURE__ */ m("aside", {
|
|
55
|
+
"aria-label": O("conversations.listPaneLabel", "Conversations list"),
|
|
56
|
+
className: n("flex flex-col min-h-0 border-r border-border-default bg-bg-surface", "w-full md:w-[320px] md:flex-shrink-0", N ? "hidden md:flex" : "flex"),
|
|
57
|
+
children: [/* @__PURE__ */ m("div", {
|
|
58
|
+
className: "sticky top-0 z-10 flex flex-col gap-3 border-b border-border-subtle bg-bg-surface px-4 pt-4 pb-3",
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ m("div", {
|
|
61
|
+
className: "flex items-center justify-between gap-2",
|
|
62
|
+
children: [/* @__PURE__ */ p("h1", {
|
|
63
|
+
className: "text-base font-semibold text-text-primary",
|
|
64
|
+
children: O("conversations.title", "Conversations")
|
|
65
|
+
}), /* @__PURE__ */ m(g, {
|
|
66
|
+
"data-tour": "conversation-new-button",
|
|
67
|
+
type: "button",
|
|
68
|
+
size: "sm",
|
|
69
|
+
onClick: F,
|
|
70
|
+
"aria-label": O("conversations.createNewConversation", "Create a new conversation"),
|
|
71
|
+
children: [/* @__PURE__ */ p(s, { size: 14 }), /* @__PURE__ */ p("span", {
|
|
72
|
+
className: "hidden sm:inline",
|
|
73
|
+
children: O("conversations.newConversation", "New")
|
|
74
|
+
})]
|
|
75
|
+
})]
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ m("div", {
|
|
78
|
+
className: "relative",
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ p(c, {
|
|
81
|
+
size: 14,
|
|
82
|
+
className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary",
|
|
83
|
+
"aria-hidden": "true"
|
|
84
|
+
}),
|
|
85
|
+
/* @__PURE__ */ p("input", {
|
|
86
|
+
"data-tour": "conversation-search",
|
|
87
|
+
type: "search",
|
|
88
|
+
value: x,
|
|
89
|
+
onChange: (e) => S(e.target.value),
|
|
90
|
+
placeholder: O("conversations.search", "Search conversations..."),
|
|
91
|
+
"aria-label": O("conversations.searchAria", "Search conversations"),
|
|
92
|
+
className: n("w-full rounded-md border border-border-default bg-bg-elevated", "py-1.5 pl-8 pr-8 text-sm text-text-primary", "placeholder:text-text-secondary", "focus:border-action-primary-border focus:outline-none focus:ring-2 focus:ring-action-primary-border/40")
|
|
93
|
+
}),
|
|
94
|
+
x.length > 0 && /* @__PURE__ */ p("button", {
|
|
95
|
+
type: "button",
|
|
96
|
+
onClick: () => S(""),
|
|
97
|
+
"aria-label": O("conversations.clearSearch", "Clear conversation search"),
|
|
98
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-text-secondary hover:bg-action-ghost-bg-hover hover:text-text-primary",
|
|
99
|
+
children: /* @__PURE__ */ p(l, { size: 12 })
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
}),
|
|
103
|
+
/* @__PURE__ */ p("div", {
|
|
104
|
+
"data-tour": "conversations-tabs",
|
|
105
|
+
children: /* @__PURE__ */ p(h, {
|
|
106
|
+
items: P,
|
|
107
|
+
activeTabId: _,
|
|
108
|
+
onTabChange: (e) => y(e),
|
|
109
|
+
variant: "underline",
|
|
110
|
+
size: "sm"
|
|
111
|
+
})
|
|
112
|
+
}),
|
|
113
|
+
C && C.length > 0 && /* @__PURE__ */ p("div", {
|
|
114
|
+
"data-tour": "conversations-filter-chips",
|
|
115
|
+
className: "flex flex-wrap gap-1.5",
|
|
116
|
+
children: C.map((e) => /* @__PURE__ */ m("button", {
|
|
117
|
+
type: "button",
|
|
118
|
+
onClick: e.onToggle,
|
|
119
|
+
"aria-pressed": e.active,
|
|
120
|
+
className: n("inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors", e.active ? "border-action-primary-border bg-action-primary-bg/10 text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-action-ghost-bg-hover hover:text-text-primary"),
|
|
121
|
+
children: [e.icon, e.label]
|
|
122
|
+
}, e.id))
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
}), /* @__PURE__ */ p("div", {
|
|
126
|
+
"data-tour": "conversations-list-scroll",
|
|
127
|
+
className: "flex-1 min-h-0 overflow-y-auto overflow-x-hidden",
|
|
128
|
+
children: w
|
|
129
|
+
})]
|
|
130
|
+
}), /* @__PURE__ */ m("section", {
|
|
131
|
+
"aria-label": O("conversations.detailPaneLabel", "Conversation detail"),
|
|
132
|
+
className: n("flex flex-col min-h-0 flex-1 min-w-0 bg-bg-surface", N ? "flex" : "hidden md:flex"),
|
|
133
|
+
children: [N && /* @__PURE__ */ p("div", {
|
|
134
|
+
className: "md:hidden border-b border-border-subtle px-3 py-2",
|
|
135
|
+
children: /* @__PURE__ */ m(g, {
|
|
136
|
+
variant: "ghost",
|
|
137
|
+
size: "sm",
|
|
138
|
+
onClick: I,
|
|
139
|
+
"aria-label": O("conversations.backToInbox", "Back to conversations list"),
|
|
140
|
+
children: [/* @__PURE__ */ p(o, { size: 14 }), O("conversations.backToInbox", "Back to inbox")]
|
|
141
|
+
})
|
|
142
|
+
}), /* @__PURE__ */ p("div", {
|
|
143
|
+
className: "flex-1 min-h-0 min-w-0 overflow-y-auto overflow-x-hidden",
|
|
144
|
+
children: T ?? E ?? /* @__PURE__ */ p(v, {
|
|
145
|
+
onNew: F,
|
|
146
|
+
title: O("conversations.detailEmpty.title", "Select a conversation"),
|
|
147
|
+
description: O("conversations.detailEmpty.description", "Pick a conversation from the list — or start a new one."),
|
|
148
|
+
newLabel: O("conversations.detailEmpty.newCta", "Start a new conversation")
|
|
149
|
+
})
|
|
150
|
+
})]
|
|
151
|
+
})]
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function v({ title: e, description: t, newLabel: n, onNew: r }) {
|
|
155
|
+
return /* @__PURE__ */ p("div", {
|
|
156
|
+
className: "flex h-full items-center justify-center p-8",
|
|
157
|
+
children: /* @__PURE__ */ m("div", {
|
|
158
|
+
className: "flex max-w-sm flex-col items-center gap-3 text-center",
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ p("div", {
|
|
161
|
+
className: "flex size-16 items-center justify-center rounded-full bg-bg-sunken",
|
|
162
|
+
children: /* @__PURE__ */ p(s, {
|
|
163
|
+
size: 28,
|
|
164
|
+
className: "text-text-secondary",
|
|
165
|
+
"aria-hidden": "true"
|
|
166
|
+
})
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ p("h2", {
|
|
169
|
+
className: "text-lg font-semibold text-text-primary",
|
|
170
|
+
children: e
|
|
171
|
+
}),
|
|
172
|
+
/* @__PURE__ */ p("p", {
|
|
173
|
+
className: "text-sm text-text-secondary",
|
|
174
|
+
children: t
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ m(g, {
|
|
177
|
+
type: "button",
|
|
178
|
+
onClick: r,
|
|
179
|
+
className: "mt-2",
|
|
180
|
+
children: [/* @__PURE__ */ p(s, { size: 14 }), n]
|
|
181
|
+
})
|
|
182
|
+
]
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
export { _ as InboxLayout };
|
|
188
|
+
|
|
189
|
+
//# sourceMappingURL=InboxLayout.js.map
|