@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,119 +1,129 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { useConversations as e } from "../providers/ConversationsProvider.js";
|
|
2
|
+
import { joinPath as t } from "../utils/navigation.js";
|
|
3
|
+
import { useTr as n } from "../utils/i18n.js";
|
|
4
|
+
import { useConversationsStore as r } from "../store/conversationsStore.js";
|
|
5
|
+
import { PermissionGuard as i } from "../components/PermissionGuard.js";
|
|
6
|
+
import { CONVERSATIONS_PERMISSIONS as a } from "../constants/permissions.js";
|
|
7
|
+
import { ConversationListPage as o } from "./ConversationListPage.js";
|
|
8
|
+
import { InboxLayout as s } from "../components/InboxLayout.js";
|
|
9
|
+
import { NewConversationDialog as c } from "../components/NewConversationDialog.js";
|
|
10
|
+
import { useUserFeatureFlags as l } from "../hooks/useUserFeatureFlags.js";
|
|
11
|
+
import { Suspense as u, lazy as d, useCallback as f, useEffect as p, useMemo as m } from "react";
|
|
12
|
+
import { Filter as h, Loader2 as g, Pin as _ } from "lucide-react";
|
|
13
|
+
import { useNavigate as v, useParams as y, useSearchParams as b } from "react-router-dom";
|
|
14
|
+
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
15
|
+
import { PageContainer as C } from "@burdenoff/fe-libs/ui";
|
|
12
16
|
//#region src/conversations/pages/ConversationsLandingPage.tsx
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
id: p,
|
|
34
|
-
label: _("conversations.allConversations", "All Conversations")
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "channels",
|
|
38
|
-
label: _("conversations.channels", "Channels")
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
id: "direct",
|
|
42
|
-
label: _("conversations.directMessages", "Direct Messages")
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "groups",
|
|
46
|
-
label: _("conversations.groups", "Groups")
|
|
47
|
-
},
|
|
48
|
-
...v ? [{
|
|
49
|
-
id: "archived",
|
|
50
|
-
label: _("conversations.archived", "Archived")
|
|
51
|
-
}] : [],
|
|
52
|
-
{
|
|
53
|
-
id: "new",
|
|
54
|
-
label: _("conversations.new", "New")
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "settings",
|
|
58
|
-
label: _("conversations.settings", "Settings")
|
|
59
|
-
}
|
|
60
|
-
], [_, v]), b = h.get("tab"), x = b === "archived" && !v ? p : b ?? p;
|
|
61
|
-
return s(() => {
|
|
62
|
-
!v && b === "archived" && g((e) => {
|
|
17
|
+
var w = d(() => import("./ConversationDetailPage.js").then((e) => ({ default: e.ConversationDetailPage }))), T = "all", E = {
|
|
18
|
+
all: void 0,
|
|
19
|
+
direct: "direct",
|
|
20
|
+
groups: "group",
|
|
21
|
+
channels: "channel",
|
|
22
|
+
archived: "archived"
|
|
23
|
+
};
|
|
24
|
+
function D() {
|
|
25
|
+
let [d, D] = b(), { conversationId: O } = y(), k = v(), { basePath: A } = e(), j = n(), { archivedEnabled: M } = l(), { searchQuery: N, setSearchQuery: P, conversationFilter: F, updateConversationFilter: I } = r(), L = d.get("new") === "true", R = f(() => {
|
|
26
|
+
D((e) => {
|
|
27
|
+
let t = new URLSearchParams(e);
|
|
28
|
+
return t.delete("new"), t;
|
|
29
|
+
}, { replace: !0 });
|
|
30
|
+
}, [D]), z = f((e) => {
|
|
31
|
+
e || R();
|
|
32
|
+
}, [R]), B = f((e) => {
|
|
33
|
+
k(t(A || "/conversations", e), { replace: !0 });
|
|
34
|
+
}, [A, k]), V = d.get("tab") ?? T, H = V === "archived" && !M ? T : V;
|
|
35
|
+
p(() => {
|
|
36
|
+
!M && V === "archived" && D((e) => {
|
|
63
37
|
let t = new URLSearchParams(e);
|
|
64
38
|
return t.delete("tab"), t;
|
|
65
39
|
}, { replace: !0 });
|
|
66
40
|
}, [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
])
|
|
71
|
-
|
|
41
|
+
M,
|
|
42
|
+
V,
|
|
43
|
+
D
|
|
44
|
+
]);
|
|
45
|
+
let U = f((e) => {
|
|
46
|
+
D((t) => {
|
|
47
|
+
let n = new URLSearchParams(t);
|
|
48
|
+
return e === T ? n.delete("tab") : n.set("tab", e), n;
|
|
49
|
+
}, { replace: !0 });
|
|
50
|
+
}, [D]), W = m(() => {
|
|
51
|
+
if (H === "archived") return [];
|
|
52
|
+
let e = F.hasUnread === !0, t = F.isPinned === !0;
|
|
53
|
+
return [{
|
|
54
|
+
id: "unread",
|
|
55
|
+
label: j("conversations.unread", "Unread"),
|
|
56
|
+
active: e,
|
|
57
|
+
onToggle: () => I({ hasUnread: e ? void 0 : !0 }),
|
|
58
|
+
icon: /* @__PURE__ */ x(h, {
|
|
59
|
+
size: 11,
|
|
60
|
+
"aria-hidden": "true"
|
|
61
|
+
})
|
|
62
|
+
}, {
|
|
63
|
+
id: "pinned",
|
|
64
|
+
label: j("conversations.pinned", "Pinned"),
|
|
65
|
+
active: t,
|
|
66
|
+
onToggle: () => I({ isPinned: t ? void 0 : !0 }),
|
|
67
|
+
icon: /* @__PURE__ */ x(_, {
|
|
68
|
+
size: 11,
|
|
69
|
+
"aria-hidden": "true"
|
|
70
|
+
})
|
|
71
|
+
}];
|
|
72
|
+
}, [
|
|
73
|
+
H,
|
|
74
|
+
F.hasUnread,
|
|
75
|
+
F.isPinned,
|
|
76
|
+
j,
|
|
77
|
+
I
|
|
78
|
+
]), G = E[H];
|
|
79
|
+
return /* @__PURE__ */ x(i, {
|
|
80
|
+
permission: a.CONVERSATION_READ,
|
|
72
81
|
allowUnavailablePermissions: !0,
|
|
73
|
-
children: /* @__PURE__ */
|
|
82
|
+
children: /* @__PURE__ */ S(C, {
|
|
74
83
|
"data-tour": "conversations-landing",
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
padded: !1,
|
|
85
|
+
className: "h-full min-h-0",
|
|
77
86
|
children: [
|
|
78
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ x("h1", {
|
|
79
88
|
id: "conversations-page-heading",
|
|
80
89
|
className: "sr-only",
|
|
81
|
-
children:
|
|
90
|
+
children: j("conversations.title", "Conversations")
|
|
82
91
|
}),
|
|
83
|
-
/* @__PURE__ */
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
/* @__PURE__ */ x(s, {
|
|
93
|
+
activeTab: H,
|
|
94
|
+
onTabChange: U,
|
|
95
|
+
archivedEnabled: M,
|
|
96
|
+
searchQuery: N,
|
|
97
|
+
onSearchChange: P,
|
|
98
|
+
filterChips: W,
|
|
99
|
+
listSlot: /* @__PURE__ */ x(o, {
|
|
100
|
+
embedded: !0,
|
|
101
|
+
forcedFilterParam: G
|
|
102
|
+
}),
|
|
103
|
+
detailSlot: O ? /* @__PURE__ */ x(u, {
|
|
104
|
+
fallback: /* @__PURE__ */ S("div", {
|
|
105
|
+
role: "status",
|
|
106
|
+
"aria-live": "polite",
|
|
107
|
+
className: "flex h-full items-center justify-center gap-2 text-sm text-text-secondary",
|
|
108
|
+
children: [/* @__PURE__ */ x(g, {
|
|
109
|
+
size: 16,
|
|
110
|
+
className: "animate-spin",
|
|
111
|
+
"aria-hidden": "true"
|
|
112
|
+
}), j("conversations.detailLoading", "Loading conversation…")]
|
|
113
|
+
}),
|
|
114
|
+
children: /* @__PURE__ */ x(w, {})
|
|
115
|
+
}) : void 0
|
|
97
116
|
}),
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
children: [
|
|
103
|
-
x === "all" && /* @__PURE__ */ u(r, {}),
|
|
104
|
-
x === "channels" && /* @__PURE__ */ u(r, { forcedFilterParam: "channel" }),
|
|
105
|
-
x === "direct" && /* @__PURE__ */ u(r, { forcedFilterParam: "direct" }),
|
|
106
|
-
x === "groups" && /* @__PURE__ */ u(r, { forcedFilterParam: "group" }),
|
|
107
|
-
x === "archived" && /* @__PURE__ */ u(m, { title: _("conversations.archivedTitle", "Archived Conversations") }),
|
|
108
|
-
x === "new" && /* @__PURE__ */ u(i, {}),
|
|
109
|
-
x === "settings" && /* @__PURE__ */ u(a, {})
|
|
110
|
-
]
|
|
117
|
+
L && /* @__PURE__ */ x(c, {
|
|
118
|
+
open: !0,
|
|
119
|
+
onOpenChange: z,
|
|
120
|
+
onCreated: B
|
|
111
121
|
})
|
|
112
122
|
]
|
|
113
123
|
})
|
|
114
124
|
});
|
|
115
125
|
}
|
|
116
126
|
//#endregion
|
|
117
|
-
export {
|
|
127
|
+
export { D as ConversationsLandingPage };
|
|
118
128
|
|
|
119
129
|
//# sourceMappingURL=ConversationsLandingPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationsLandingPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationsLandingPage.tsx"],"sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { Tabs, type TabItem } from \"@burdenoff/fe-libs/chrome\";\nimport { ConversationListPage } from \"./ConversationListPage\";\nimport { NewConversationPage } from \"./NewConversationPage\";\nimport { ConversationsSettingsPage } from \"./ConversationsSettingsPage\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { useUserFeatureFlags } from \"../hooks/useUserFeatureFlags\";\nimport { useTr } from \"../utils/i18n\";\n\nconst DEFAULT_TAB_ID = \"all\";\n\n/**\n * Coming-soon placeholder shown for tabs whose dedicated pages\n * are not yet implemented in this microfrontend.\n */\nfunction ComingSoon({ title }: { title: string }) {\n const tr = useTr();\n\n return (\n <div className=\"flex h-full items-center justify-center p-8 text-center\">\n <div className=\"space-y-2\">\n <h3 className=\"text-lg font-semibold text-foreground\">{title}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\"conversations.comingSoon\", \"Coming soon.\")}\n </p>\n </div>\n </div>\n );\n}\n\n/**\n * Tabbed landing page for the Conversations microfrontend.\n *\n * Renders an underline tab bar at the top and swaps the active panel\n * based on the `?tab=...` URL search param. The first tab (\"all\") uses\n * a clean URL with no `tab` param.\n */\nexport function ConversationsLandingPage() {\n const [params, setParams] = useSearchParams();\n const tr = useTr();\n\n const { archivedEnabled } = useUserFeatureFlags();\n\n const tabs: TabItem[] = useMemo(\n () => [\n {\n id: DEFAULT_TAB_ID,\n label: tr(\"conversations.allConversations\", \"All Conversations\"),\n },\n { id: \"channels\", label: tr(\"conversations.channels\", \"Channels\") },\n {\n id: \"direct\",\n label: tr(\"conversations.directMessages\", \"Direct Messages\"),\n },\n { id: \"groups\", label: tr(\"conversations.groups\", \"Groups\") },\n ...(archivedEnabled\n ? [{ id: \"archived\", label: tr(\"conversations.archived\", \"Archived\") }]\n : []),\n { id: \"new\", label: tr(\"conversations.new\", \"New\") },\n { id: \"settings\", label: tr(\"conversations.settings\", \"Settings\") },\n ],\n [tr, archivedEnabled],\n );\n\n const currentTab = params.get(\"tab\");\n // Render `activeId` correctly for the archived-off case; the effect below cleans the URL lazily.\n const activeId =\n currentTab === \"archived\" && !archivedEnabled\n ? DEFAULT_TAB_ID\n : (currentTab ?? DEFAULT_TAB_ID);\n\n useEffect(() => {\n if (!archivedEnabled && currentTab === \"archived\") {\n // Updater form reads the freshest params at flush time to preserve concurrent query-param changes.\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n next.delete(\"tab\");\n return next;\n },\n { replace: true },\n );\n }\n }, [archivedEnabled, currentTab, setParams]);\n\n const handleTabChange = (id: string) => {\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n if (id === DEFAULT_TAB_ID) {\n next.delete(\"tab\");\n } else {\n next.set(\"tab\", id);\n }\n return next;\n },\n { replace: true },\n );\n };\n\n return (\n <PermissionGuard\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n allowUnavailablePermissions\n >\n <main\n data-tour=\"conversations-landing\"\n className=\"flex flex-col h-full\"\n aria-labelledby=\"conversations-page-heading\"\n >\n <h1 id=\"conversations-page-heading\" className=\"sr-only\">\n {tr(\"conversations.title\", \"Conversations\")}\n </h1>\n <div\n data-tour=\"conversations-tabs\"\n className=\"border-b border-border-subtle px-4 pt-3\"\n >\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n <section\n className=\"flex-1 min-h-0 overflow-auto\"\n aria-live=\"polite\"\n aria-label={tr(\n \"conversations.activePanel\",\n \"Active conversations panel\",\n )}\n >\n {activeId === \"all\" && <ConversationListPage />}\n {activeId === \"channels\" && (\n <ConversationListPage forcedFilterParam=\"channel\" />\n )}\n {activeId === \"direct\" && (\n <ConversationListPage forcedFilterParam=\"direct\" />\n )}\n {activeId === \"groups\" && (\n <ConversationListPage forcedFilterParam=\"group\" />\n )}\n {activeId === \"archived\" && (\n <ComingSoon\n title={tr(\n \"conversations.archivedTitle\",\n \"Archived Conversations\",\n )}\n />\n )}\n {activeId === \"new\" && <NewConversationPage />}\n {activeId === \"settings\" && <ConversationsSettingsPage />}\n </section>\n </main>\n </PermissionGuard>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAM,IAAiB;AAMvB,SAAS,EAAW,EAAE,YAA4B;CAChD,IAAM,IAAK,GAAO;AAElB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cAAyC;IAAW,CAAA,EAClE,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,4BAA4B,eAAe;IAC7C,CAAA,CACA;;EACF,CAAA;;AAWV,SAAgB,IAA2B;CACzC,IAAM,CAAC,GAAQ,KAAa,GAAiB,EACvC,IAAK,GAAO,EAEZ,EAAE,uBAAoB,GAAqB,EAE3C,IAAkB,QAChB;EACJ;GACE,IAAI;GACJ,OAAO,EAAG,kCAAkC,oBAAoB;GACjE;EACD;GAAE,IAAI;GAAY,OAAO,EAAG,0BAA0B,WAAW;GAAE;EACnE;GACE,IAAI;GACJ,OAAO,EAAG,gCAAgC,kBAAkB;GAC7D;EACD;GAAE,IAAI;GAAU,OAAO,EAAG,wBAAwB,SAAS;GAAE;EAC7D,GAAI,IACA,CAAC;GAAE,IAAI;GAAY,OAAO,EAAG,0BAA0B,WAAW;GAAE,CAAC,GACrE,EAAE;EACN;GAAE,IAAI;GAAO,OAAO,EAAG,qBAAqB,MAAM;GAAE;EACpD;GAAE,IAAI;GAAY,OAAO,EAAG,0BAA0B,WAAW;GAAE;EACpE,EACD,CAAC,GAAI,EAAgB,CACtB,EAEK,IAAa,EAAO,IAAI,MAAM,EAE9B,IACJ,MAAe,cAAc,CAAC,IAC1B,IACC,KAAc;AA+BrB,QA7BA,QAAgB;AACd,EAAI,CAAC,KAAmB,MAAe,cAErC,GACG,MAAS;GACR,IAAM,IAAO,IAAI,gBAAgB,EAAK;AAEtC,UADA,EAAK,OAAO,MAAM,EACX;KAET,EAAE,SAAS,IAAM,CAClB;IAEF;EAAC;EAAiB;EAAY;EAAU,CAAC,EAkB1C,kBAAC,GAAD;EACE,YAAY,EAA0B;EACtC,6BAAA;YAEA,kBAAC,QAAD;GACE,aAAU;GACV,WAAU;GACV,mBAAgB;aAHlB;IAKE,kBAAC,MAAD;KAAI,IAAG;KAA6B,WAAU;eAC3C,EAAG,uBAAuB,gBAAgB;KACxC,CAAA;IACL,kBAAC,OAAD;KACE,aAAU;KACV,WAAU;eAEV,kBAAC,GAAD;MACE,OAAO;MACP,aAAa;MACb,cAnCe,MAAe;AACtC,UACG,MAAS;QACR,IAAM,IAAO,IAAI,gBAAgB,EAAK;AAMtC,eALI,MAAO,IACT,EAAK,OAAO,MAAM,GAElB,EAAK,IAAI,OAAO,EAAG,EAEd;UAET,EAAE,SAAS,IAAM,CAClB;;MAwBO,SAAQ;MACR,CAAA;KACE,CAAA;IACN,kBAAC,WAAD;KACE,WAAU;KACV,aAAU;KACV,cAAY,EACV,6BACA,6BACD;eANH;MAQG,MAAa,SAAS,kBAAC,GAAD,EAAwB,CAAA;MAC9C,MAAa,cACZ,kBAAC,GAAD,EAAsB,mBAAkB,WAAY,CAAA;MAErD,MAAa,YACZ,kBAAC,GAAD,EAAsB,mBAAkB,UAAW,CAAA;MAEpD,MAAa,YACZ,kBAAC,GAAD,EAAsB,mBAAkB,SAAU,CAAA;MAEnD,MAAa,cACZ,kBAAC,GAAD,EACE,OAAO,EACL,+BACA,yBACD,EACD,CAAA;MAEH,MAAa,SAAS,kBAAC,GAAD,EAAuB,CAAA;MAC7C,MAAa,cAAc,kBAAC,GAAD,EAA6B,CAAA;MACjD;;IACL;;EACS,CAAA"}
|
|
1
|
+
{"version":3,"file":"ConversationsLandingPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationsLandingPage.tsx"],"sourcesContent":["import { Suspense, useCallback, useEffect, useMemo, lazy } from \"react\";\nimport { useNavigate, useParams, useSearchParams } from \"react-router-dom\";\nimport { PageContainer } from \"@burdenoff/fe-libs/ui\";\nimport { Pin, Filter, Loader2 } from \"lucide-react\";\nimport { ConversationListPage } from \"./ConversationListPage\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport {\n InboxLayout,\n type FilterChipDescriptor,\n type InboxTabId,\n} from \"../components/InboxLayout\";\nimport { NewConversationDialog } from \"../components/NewConversationDialog\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { useUserFeatureFlags } from \"../hooks/useUserFeatureFlags\";\nimport { useConversationsStore } from \"../store/conversationsStore\";\nimport { useTr } from \"../utils/i18n\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { joinPath } from \"../utils/navigation\";\n\n// Lazy-load the detail page so the inbox bundle stays small for users who\n// never open a conversation in this session. The detail subtree is the\n// heaviest by far (~19kB gz including MessageBubble).\nconst ConversationDetailPage = lazy(() =>\n import(\"./ConversationDetailPage\").then((m) => ({\n default: m.ConversationDetailPage,\n })),\n);\n\nconst DEFAULT_TAB_ID: InboxTabId = \"all\";\n\n// Maps the inbox tab id to the `forcedFilterParam` prop accepted by\n// ConversationListPage. The \"all\" tab passes undefined so the list shows\n// every conversation type. The archived tab triggers the BE state filter\n// (states: [ARCHIVED]) handled inside ConversationListPage.\nconst TAB_TO_LIST_FILTER: Record<\n InboxTabId,\n \"channel\" | \"direct\" | \"group\" | \"archived\" | undefined\n> = {\n all: undefined,\n direct: \"direct\",\n groups: \"group\",\n channels: \"channel\",\n archived: \"archived\",\n};\n\n/**\n * Landing page for `/conversations`.\n *\n * Renders the canonical inbox: two-pane shell with the conversation list on\n * the left and an empty detail panel on the right. The detail panel is\n * filled in by the router on `/conversations/:conversationId`; this page\n * itself only renders the chrome and the list.\n *\n * Tabs (All / Direct / Groups / Channels / Archived) are stored in the\n * `?tab=` URL search param so refreshing the page keeps the user on the\n * same view.\n */\nexport function ConversationsLandingPage() {\n const [params, setParams] = useSearchParams();\n const { conversationId } = useParams<{ conversationId?: string }>();\n const navigate = useNavigate();\n const { basePath } = useConversations();\n const tr = useTr();\n const { archivedEnabled } = useUserFeatureFlags();\n const {\n searchQuery,\n setSearchQuery,\n conversationFilter,\n updateConversationFilter,\n } = useConversationsStore();\n\n // `?new=true` opens the create-conversation dialog in-place. The dialog\n // is dismissed by either (a) the user closing it, in which case we drop\n // the param via setParams (replace), or (b) a successful create, in\n // which case we strip the param and navigate to the new detail route.\n const isNewDialogOpen = params.get(\"new\") === \"true\";\n\n const closeNewDialog = useCallback(() => {\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n next.delete(\"new\");\n return next;\n },\n { replace: true },\n );\n }, [setParams]);\n\n const handleNewDialogOpenChange = useCallback(\n (open: boolean) => {\n if (!open) closeNewDialog();\n },\n [closeNewDialog],\n );\n\n const handleNewDialogCreated = useCallback(\n (newConversationId: string) => {\n // The dialog closes itself before invoking onCreated; we just need\n // to route to the new conversation detail page (which lives under\n // the conversations basePath) without the lingering ?new=true.\n navigate(joinPath(basePath || \"/conversations\", newConversationId), {\n replace: true,\n });\n },\n [basePath, navigate],\n );\n\n const requestedTab = (params.get(\"tab\") ?? DEFAULT_TAB_ID) as InboxTabId;\n // Strip the archived tab when the feature flag is off — the side effect\n // below also lazily cleans the URL so a deep link doesn't leave the user\n // staring at an unknown tab.\n const activeTab: InboxTabId =\n requestedTab === \"archived\" && !archivedEnabled\n ? DEFAULT_TAB_ID\n : requestedTab;\n\n useEffect(() => {\n if (!archivedEnabled && requestedTab === \"archived\") {\n // Updater form reads the freshest params at flush time to preserve\n // concurrent query-param changes elsewhere in the tree.\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n next.delete(\"tab\");\n return next;\n },\n { replace: true },\n );\n }\n }, [archivedEnabled, requestedTab, setParams]);\n\n const handleTabChange = useCallback(\n (id: InboxTabId) => {\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n if (id === DEFAULT_TAB_ID) {\n next.delete(\"tab\");\n } else {\n next.set(\"tab\", id);\n }\n return next;\n },\n { replace: true },\n );\n },\n [setParams],\n );\n\n // Filter chips are only meaningful on tabs where they can hide items. On\n // the Archived tab the only sane secondary toggle is \"Pinned\" (unread\n // doesn't make sense for archived conversations); on the inbox tabs we\n // expose both unread + pinned.\n const filterChips = useMemo<FilterChipDescriptor[]>(() => {\n if (activeTab === \"archived\") return [];\n const unreadActive = conversationFilter.hasUnread === true;\n const pinnedActive = conversationFilter.isPinned === true;\n return [\n {\n id: \"unread\",\n label: tr(\"conversations.unread\", \"Unread\"),\n active: unreadActive,\n onToggle: () =>\n updateConversationFilter({\n hasUnread: unreadActive ? undefined : true,\n }),\n icon: <Filter size={11} aria-hidden=\"true\" />,\n },\n {\n id: \"pinned\",\n label: tr(\"conversations.pinned\", \"Pinned\"),\n active: pinnedActive,\n onToggle: () =>\n updateConversationFilter({\n isPinned: pinnedActive ? undefined : true,\n }),\n icon: <Pin size={11} aria-hidden=\"true\" />,\n },\n ];\n }, [\n activeTab,\n conversationFilter.hasUnread,\n conversationFilter.isPinned,\n tr,\n updateConversationFilter,\n ]);\n\n const forcedFilter = TAB_TO_LIST_FILTER[activeTab];\n\n return (\n <PermissionGuard\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n allowUnavailablePermissions\n >\n <PageContainer\n data-tour=\"conversations-landing\"\n padded={false}\n className=\"h-full min-h-0\"\n >\n <h1 id=\"conversations-page-heading\" className=\"sr-only\">\n {tr(\"conversations.title\", \"Conversations\")}\n </h1>\n <InboxLayout\n activeTab={activeTab}\n onTabChange={handleTabChange}\n archivedEnabled={archivedEnabled}\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n filterChips={filterChips}\n listSlot={\n <ConversationListPage embedded forcedFilterParam={forcedFilter} />\n }\n detailSlot={\n conversationId ? (\n <Suspense\n fallback={\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"flex h-full items-center justify-center gap-2 text-sm text-text-secondary\"\n >\n <Loader2\n size={16}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"conversations.detailLoading\", \"Loading conversation…\")}\n </div>\n }\n >\n <ConversationDetailPage />\n </Suspense>\n ) : undefined\n }\n />\n {isNewDialogOpen && (\n <NewConversationDialog\n open\n onOpenChange={handleNewDialogOpenChange}\n onCreated={handleNewDialogCreated}\n />\n )}\n </PageContainer>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|