@burdenoff/microfe-comradecircle 2026.525.1 → 2026.704.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/comradecircle/ComradeCircleRoot.js +75 -95
- package/dist/comradecircle/ComradeCircleRoot.js.map +1 -1
- package/dist/comradecircle/ComradeCircleRoutes.js +58 -37
- package/dist/comradecircle/ComradeCircleRoutes.js.map +1 -1
- package/dist/comradecircle/components/Avatar.js +47 -0
- package/dist/comradecircle/components/Avatar.js.map +1 -0
- package/dist/comradecircle/components/AvatarUploadField.js +80 -0
- package/dist/comradecircle/components/AvatarUploadField.js.map +1 -0
- package/dist/comradecircle/components/CircleCard.js +118 -0
- package/dist/comradecircle/components/CircleCard.js.map +1 -0
- package/dist/comradecircle/components/CommentThread.js +105 -0
- package/dist/comradecircle/components/CommentThread.js.map +1 -0
- package/dist/comradecircle/components/CreateCircleDialog.js +159 -0
- package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -0
- package/dist/comradecircle/components/CreateEventDialog.js +210 -0
- package/dist/comradecircle/components/CreateEventDialog.js.map +1 -0
- package/dist/comradecircle/components/EmptyState.js +35 -0
- package/dist/comradecircle/components/EmptyState.js.map +1 -0
- package/dist/comradecircle/components/EventCard.js +93 -0
- package/dist/comradecircle/components/EventCard.js.map +1 -0
- package/dist/comradecircle/components/FileImage.js +20 -0
- package/dist/comradecircle/components/FileImage.js.map +1 -0
- package/dist/comradecircle/components/FilterTabs.js +27 -0
- package/dist/comradecircle/components/FilterTabs.js.map +1 -0
- package/dist/comradecircle/components/ImageAttachments.js +97 -0
- package/dist/comradecircle/components/ImageAttachments.js.map +1 -0
- package/dist/comradecircle/components/MemberIdentity.js +44 -0
- package/dist/comradecircle/components/MemberIdentity.js.map +1 -0
- package/dist/comradecircle/components/MiniBar.js +22 -0
- package/dist/comradecircle/components/MiniBar.js.map +1 -0
- package/dist/comradecircle/components/PageLayout.js +38 -0
- package/dist/comradecircle/components/PageLayout.js.map +1 -0
- package/dist/comradecircle/components/Pagination.js +50 -0
- package/dist/comradecircle/components/Pagination.js.map +1 -0
- package/dist/comradecircle/components/PollCard.js +97 -0
- package/dist/comradecircle/components/PollCard.js.map +1 -0
- package/dist/comradecircle/components/PostCard.js +163 -0
- package/dist/comradecircle/components/PostCard.js.map +1 -0
- package/dist/comradecircle/components/PostComposer.js +87 -0
- package/dist/comradecircle/components/PostComposer.js.map +1 -0
- package/dist/comradecircle/components/ReactionBar.js +122 -0
- package/dist/comradecircle/components/ReactionBar.js.map +1 -0
- package/dist/comradecircle/components/StateViews.js +49 -0
- package/dist/comradecircle/components/StateViews.js.map +1 -0
- package/dist/comradecircle/components/StatusBadge.js +68 -0
- package/dist/comradecircle/components/StatusBadge.js.map +1 -0
- package/dist/comradecircle/components/Tabs.js +31 -0
- package/dist/comradecircle/components/Tabs.js.map +1 -0
- package/dist/comradecircle/components/TagInput.js +49 -0
- package/dist/comradecircle/components/TagInput.js.map +1 -0
- package/dist/comradecircle/context/ComradeCircleContext.js +85 -0
- package/dist/comradecircle/context/ComradeCircleContext.js.map +1 -0
- package/dist/comradecircle/context/index.js +1 -0
- package/dist/comradecircle/hooks/index.js +3 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.js +419 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -0
- package/dist/comradecircle/hooks/useCursorPager.js +25 -0
- package/dist/comradecircle/hooks/useCursorPager.js.map +1 -0
- package/dist/comradecircle/hooks/useFiles.js +78 -0
- package/dist/comradecircle/hooks/useFiles.js.map +1 -0
- package/dist/comradecircle/index.js +5 -2
- package/dist/comradecircle/lib/graphql-cache.js +9 -0
- package/dist/comradecircle/lib/graphql-cache.js.map +1 -0
- package/dist/comradecircle/lib/toast.js +39 -0
- package/dist/comradecircle/lib/toast.js.map +1 -0
- package/dist/comradecircle/pages/FeedPage.js +65 -0
- package/dist/comradecircle/pages/FeedPage.js.map +1 -0
- package/dist/comradecircle/pages/circles/CircleDetailPage.js +651 -0
- package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -0
- package/dist/comradecircle/pages/circles/CirclesPage.js +129 -0
- package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -0
- package/dist/comradecircle/pages/events/EventDetailPage.js +276 -0
- package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -0
- package/dist/comradecircle/pages/events/EventsPage.js +102 -0
- package/dist/comradecircle/pages/events/EventsPage.js.map +1 -0
- package/dist/comradecircle/pages/members/MemberProfilePage.js +138 -0
- package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -0
- package/dist/comradecircle/pages/members/MembersPage.js +153 -0
- package/dist/comradecircle/pages/members/MembersPage.js.map +1 -0
- package/dist/comradecircle/pages/profile/MyProfilePage.js +266 -0
- package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -0
- package/dist/comradecircle/utils/cn.js +10 -0
- package/dist/comradecircle/utils/cn.js.map +1 -0
- package/dist/comradecircle/utils/formatters.js +51 -0
- package/dist/comradecircle/utils/formatters.js.map +1 -0
- package/dist/comradecircle/utils/navigation.js +2 -1
- package/dist/comradecircle/utils/navigation.js.map +1 -1
- package/dist/comradecircle/utils/slug.js +8 -0
- package/dist/comradecircle/utils/slug.js.map +1 -0
- package/dist/index.js +6 -4
- package/dist/node_modules/clsx/dist/clsx.js +18 -0
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/sonner/dist/index.js +163 -0
- package/dist/node_modules/sonner/dist/index.js.map +1 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +3 -2
- package/dist/comradecircle/pages/CommunitiesPage.js +0 -299
- package/dist/comradecircle/pages/CommunitiesPage.js.map +0 -1
- package/dist/comradecircle/pages/CreatorPage.js +0 -399
- package/dist/comradecircle/pages/CreatorPage.js.map +0 -1
- package/dist/comradecircle/pages/DashboardPage.js +0 -265
- package/dist/comradecircle/pages/DashboardPage.js.map +0 -1
- package/dist/comradecircle/providers/ComradeCircleProvider.js +0 -27
- package/dist/comradecircle/providers/ComradeCircleProvider.js.map +0 -1
- package/dist/comradecircle/store/comradecircleStore.js +0 -345
- package/dist/comradecircle/store/comradecircleStore.js.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import "../../context/index.js";
|
|
4
|
+
import { useComEvents as n } from "../../hooks/useComradeCircleApi.js";
|
|
5
|
+
import { useCursorPager as r } from "../../hooks/useCursorPager.js";
|
|
6
|
+
import "../../hooks/index.js";
|
|
7
|
+
import { EmptyState as i } from "../../components/EmptyState.js";
|
|
8
|
+
import { ErrorView as a, LoadingView as o } from "../../components/StateViews.js";
|
|
9
|
+
import { Pagination as s } from "../../components/Pagination.js";
|
|
10
|
+
import { EventCard as c } from "../../components/EventCard.js";
|
|
11
|
+
import { CreateEventDialog as l } from "../../components/CreateEventDialog.js";
|
|
12
|
+
import { FilterTabs as u } from "../../components/FilterTabs.js";
|
|
13
|
+
import { useState as d } from "react";
|
|
14
|
+
import { CalendarDays as f, Plus as p } from "lucide-react";
|
|
15
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
16
|
+
import { Button as g } from "@burdenoff/fe-libs/ui";
|
|
17
|
+
//#region src/comradecircle/pages/events/EventsPage.tsx
|
|
18
|
+
var _ = 12, v = [
|
|
19
|
+
{
|
|
20
|
+
value: "ALL",
|
|
21
|
+
label: "All"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
value: "SCHEDULED",
|
|
25
|
+
label: "Scheduled"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: "LIVE",
|
|
29
|
+
label: "Live"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
value: "COMPLETED",
|
|
33
|
+
label: "Completed"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
value: "CANCELLED",
|
|
37
|
+
label: "Cancelled"
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
function y() {
|
|
41
|
+
let { navigateTo: y } = e(), b = r(), [x, S] = d("ALL"), [C, w] = d(!1), { data: T, isLoading: E, isError: D, refetch: O } = n({
|
|
42
|
+
status: x === "ALL" ? void 0 : x,
|
|
43
|
+
pagination: {
|
|
44
|
+
limit: _,
|
|
45
|
+
cursor: b.cursor
|
|
46
|
+
}
|
|
47
|
+
}), k = T?.items ?? [];
|
|
48
|
+
return /* @__PURE__ */ h(t, {
|
|
49
|
+
title: "Events",
|
|
50
|
+
description: "Upcoming and past gatherings across all the circles you belong to.",
|
|
51
|
+
actions: /* @__PURE__ */ h(g, {
|
|
52
|
+
onClick: () => w(!0),
|
|
53
|
+
children: [/* @__PURE__ */ m(p, { className: "mr-1.5 h-4 w-4" }), " New event"]
|
|
54
|
+
}),
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ m(u, {
|
|
57
|
+
options: v,
|
|
58
|
+
value: x,
|
|
59
|
+
onChange: (e) => {
|
|
60
|
+
S(e), b.reset();
|
|
61
|
+
},
|
|
62
|
+
className: "mb-5"
|
|
63
|
+
}),
|
|
64
|
+
E ? /* @__PURE__ */ m(o, { label: "Loading events…" }) : D ? /* @__PURE__ */ m(a, {
|
|
65
|
+
message: "We couldn't load events.",
|
|
66
|
+
onRetry: () => O()
|
|
67
|
+
}) : k.length === 0 && b.page === 0 ? /* @__PURE__ */ m(i, {
|
|
68
|
+
illustration: "empty-data",
|
|
69
|
+
icon: /* @__PURE__ */ m(f, { className: "h-10 w-10" }),
|
|
70
|
+
title: x === "ALL" ? "No events yet" : "No events match this filter",
|
|
71
|
+
description: x === "ALL" ? "Schedule a gathering for one of your circles — online, in person, or hybrid." : "Try a different status, or schedule a new gathering for one of your circles.",
|
|
72
|
+
action: /* @__PURE__ */ h(g, {
|
|
73
|
+
onClick: () => w(!0),
|
|
74
|
+
children: [/* @__PURE__ */ m(p, { className: "mr-1.5 h-4 w-4" }), " New event"]
|
|
75
|
+
})
|
|
76
|
+
}) : /* @__PURE__ */ h("div", {
|
|
77
|
+
className: "flex flex-col gap-4",
|
|
78
|
+
children: [/* @__PURE__ */ m("div", {
|
|
79
|
+
className: "grid gap-4 md:grid-cols-2",
|
|
80
|
+
children: k.map((e) => /* @__PURE__ */ m(c, {
|
|
81
|
+
event: e,
|
|
82
|
+
onOpen: () => y("events/" + e.id)
|
|
83
|
+
}, e.id))
|
|
84
|
+
}), /* @__PURE__ */ m(s, {
|
|
85
|
+
page: b.page,
|
|
86
|
+
pageSize: _,
|
|
87
|
+
itemCount: k.length,
|
|
88
|
+
hasMore: T?.pageInfo.hasMore ?? !1,
|
|
89
|
+
onPageChange: (e) => b.onPageChange(e, T?.pageInfo.cursor)
|
|
90
|
+
})]
|
|
91
|
+
}),
|
|
92
|
+
/* @__PURE__ */ m(l, {
|
|
93
|
+
open: C,
|
|
94
|
+
onOpenChange: w
|
|
95
|
+
})
|
|
96
|
+
]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { y as EventsPage };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=EventsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventsPage.js","names":[],"sources":["../../../../src/comradecircle/pages/events/EventsPage.tsx"],"sourcesContent":["/**\n * EventsPage — every event across the circles the member belongs to.\n * @module comradecircle/pages/events\n *\n * Reads `comListEvents` (all circles, newest-scheduled first) with a lifecycle\n * status filter (scheduled → live → completed → cancelled). Scheduling a new\n * event opens the shared {@link CreateEventDialog} without a fixed circle, so it\n * shows its own active-circle picker.\n */\n\nimport { useState } from 'react';\nimport { CalendarDays, Plus } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { EventCard } from '../../components/EventCard';\nimport { CreateEventDialog } from '../../components/CreateEventDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useComEvents, useCursorPager } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport type { ComEventStatus } from '../../types';\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = 'ALL' | ComEventStatus;\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LIVE', label: 'Live' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nexport function EventsPage() {\n const { navigateTo } = useComradeCircle();\n const pager = useCursorPager();\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const { data, isLoading, isError, refetch } = useComEvents({\n status: status === 'ALL' ? undefined : status,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const events = data?.items ?? [];\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n return (\n <PageLayout\n title=\"Events\"\n description=\"Upcoming and past gatherings across all the circles you belong to.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n }\n >\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={changeStatus}\n className=\"mb-5\"\n />\n\n {isLoading ? (\n <LoadingView label=\"Loading events…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load events.\" onRetry={() => refetch()} />\n ) : events.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title={status === 'ALL' ? 'No events yet' : 'No events match this filter'}\n description={\n status === 'ALL'\n ? 'Schedule a gathering for one of your circles — online, in person, or hybrid.'\n : 'Try a different status, or schedule a new gathering for one of your circles.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n <div className=\"grid gap-4 md:grid-cols-2\">\n {events.map((event) => (\n <EventCard\n key={event.id}\n event={event}\n onOpen={() => navigateTo('events/' + event.id)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={events.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n\n <CreateEventDialog open={createOpen} onOpenChange={setCreateOpen} />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,IAAM,IAAY,IAIZ,IAA+C;CACnD;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAQ,OAAO;EAAQ;CAChC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAa,OAAO;EAAa;CAC3C;AAED,SAAgB,IAAa;CAC3B,IAAM,EAAE,kBAAe,GAAkB,EACnC,IAAQ,GAAgB,EACxB,CAAC,GAAQ,KAAa,EAAuB,MAAM,EACnD,CAAC,GAAY,KAAiB,EAAS,GAAM,EAE7C,EAAE,SAAM,cAAW,YAAS,eAAY,EAAa;EACzD,QAAQ,MAAW,QAAQ,KAAA,IAAY;EACvC,YAAY;GAAE,OAAO;GAAW,QAAQ,EAAM;GAAQ;EACvD,CAAC,EAEI,IAAS,GAAM,SAAS,EAAE;AAOhC,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,eAAe,EAAc,GAAK;aAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,aAC5B;;YANb;GASE,kBAAC,GAAD;IACE,SAAS;IACT,OAAO;IACP,WAlBgB,MAAuB;AAE3C,KADA,EAAU,EAAK,EACf,EAAM,OAAO;;IAiBT,WAAU;IACV,CAAA;GAED,IACC,kBAAC,GAAD,EAAa,OAAM,mBAAoB,CAAA,GACrC,IACF,kBAAC,GAAD;IAAW,SAAQ;IAA2B,eAAe,GAAS;IAAI,CAAA,GACxE,EAAO,WAAW,KAAK,EAAM,SAAS,IACxC,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAc,WAAU,aAAc,CAAA;IAC5C,OAAO,MAAW,QAAQ,kBAAkB;IAC5C,aACE,MAAW,QACP,iFACA;IAEN,QACE,kBAAC,GAAD;KAAQ,eAAe,EAAc,GAAK;eAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,aAC5B;;IAEX,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAO,KAAK,MACX,kBAAC,GAAD;MAES;MACP,cAAc,EAAW,YAAY,EAAM,GAAG;MAC9C,EAHK,EAAM,GAGX,CACF;KACE,CAAA,EACN,kBAAC,GAAD;KACE,MAAM,EAAM;KACZ,UAAU;KACV,WAAW,EAAO;KAClB,SAAS,GAAM,SAAS,WAAW;KACnC,eAAe,MAAW,EAAM,aAAa,GAAQ,GAAM,SAAS,OAAO;KAC3E,CAAA,CACE;;GAGR,kBAAC,GAAD;IAAmB,MAAM;IAAY,cAAc;IAAiB,CAAA;GACzD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import "../../context/index.js";
|
|
4
|
+
import { useComFollow as n, useComMyFollows as r, useComProfile as i, useComUnfollow as a } from "../../hooks/useComradeCircleApi.js";
|
|
5
|
+
import "../../hooks/index.js";
|
|
6
|
+
import { Avatar as o } from "../../components/Avatar.js";
|
|
7
|
+
import { describeError as s, notify as c } from "../../lib/toast.js";
|
|
8
|
+
import { EmptyState as l } from "../../components/EmptyState.js";
|
|
9
|
+
import { ErrorView as u, LoadingView as d, Section as f } from "../../components/StateViews.js";
|
|
10
|
+
import { StatusBadge as p } from "../../components/StatusBadge.js";
|
|
11
|
+
import { ArrowLeft as m, Pencil as h, UserCheck as g, UserPlus as _, UserX as v } from "lucide-react";
|
|
12
|
+
import { useParams as y } from "react-router-dom";
|
|
13
|
+
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
14
|
+
import { Button as S } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
//#region src/comradecircle/pages/members/MemberProfilePage.tsx
|
|
16
|
+
function C({ items: e }) {
|
|
17
|
+
return /* @__PURE__ */ b("div", {
|
|
18
|
+
className: "flex flex-wrap gap-1.5",
|
|
19
|
+
children: e.map((e) => /* @__PURE__ */ b("span", {
|
|
20
|
+
className: "rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
|
|
21
|
+
children: e
|
|
22
|
+
}, e))
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function w() {
|
|
26
|
+
let { memberId: w } = y(), { navigateTo: T, currentUser: E } = e(), { data: D, isLoading: O, isError: k, refetch: A } = i(w), j = r("MEMBER"), M = n(), N = a(), P = /* @__PURE__ */ x(S, {
|
|
27
|
+
variant: "ghost",
|
|
28
|
+
size: "sm",
|
|
29
|
+
onClick: () => T("members"),
|
|
30
|
+
children: [/* @__PURE__ */ b(m, { className: "mr-1.5 h-4 w-4" }), " Back"]
|
|
31
|
+
});
|
|
32
|
+
if (O) return /* @__PURE__ */ b(t, {
|
|
33
|
+
title: "Profile",
|
|
34
|
+
actions: P,
|
|
35
|
+
children: /* @__PURE__ */ b(d, { label: "Loading profile…" })
|
|
36
|
+
});
|
|
37
|
+
if (k) return /* @__PURE__ */ b(t, {
|
|
38
|
+
title: "Profile",
|
|
39
|
+
actions: P,
|
|
40
|
+
children: /* @__PURE__ */ b(u, {
|
|
41
|
+
message: "We couldn't load this member's profile.",
|
|
42
|
+
onRetry: () => A()
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
if (!D) return /* @__PURE__ */ b(t, {
|
|
46
|
+
title: "Profile",
|
|
47
|
+
actions: P,
|
|
48
|
+
children: /* @__PURE__ */ b(l, {
|
|
49
|
+
icon: /* @__PURE__ */ b(v, { className: "h-10 w-10" }),
|
|
50
|
+
title: "No profile",
|
|
51
|
+
description: "This member hasn't set up a ComradeCircle profile yet."
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
let F = !!(w && E?.id === w), I = (j.data ?? []).some((e) => e.targetId === w && e.targetType === "MEMBER"), L = M.isPending || N.isPending, R = async () => {
|
|
55
|
+
if (w) try {
|
|
56
|
+
I ? (await N.mutateAsync({
|
|
57
|
+
targetId: w,
|
|
58
|
+
targetType: "MEMBER"
|
|
59
|
+
}), c.success("Unfollowed", D.displayName)) : (await M.mutateAsync({
|
|
60
|
+
targetId: w,
|
|
61
|
+
targetType: "MEMBER"
|
|
62
|
+
}), c.success("Following", D.displayName));
|
|
63
|
+
} catch (e) {
|
|
64
|
+
c.error("Could not update follow", s(e));
|
|
65
|
+
}
|
|
66
|
+
}, z = F ? /* @__PURE__ */ x(S, {
|
|
67
|
+
variant: "outline",
|
|
68
|
+
onClick: () => T("profile"),
|
|
69
|
+
children: [/* @__PURE__ */ b(h, { className: "mr-1.5 h-4 w-4" }), " Edit profile"]
|
|
70
|
+
}) : I ? /* @__PURE__ */ x(S, {
|
|
71
|
+
variant: "outline",
|
|
72
|
+
onClick: R,
|
|
73
|
+
disabled: L,
|
|
74
|
+
children: [/* @__PURE__ */ b(g, { className: "mr-1.5 h-4 w-4" }), " Following"]
|
|
75
|
+
}) : /* @__PURE__ */ x(S, {
|
|
76
|
+
onClick: R,
|
|
77
|
+
disabled: L,
|
|
78
|
+
children: [/* @__PURE__ */ b(_, { className: "mr-1.5 h-4 w-4" }), " Follow"]
|
|
79
|
+
});
|
|
80
|
+
return /* @__PURE__ */ b(t, {
|
|
81
|
+
title: "Profile",
|
|
82
|
+
actions: P,
|
|
83
|
+
children: /* @__PURE__ */ x("div", {
|
|
84
|
+
className: "mx-auto flex max-w-2xl flex-col gap-6",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ x("div", {
|
|
87
|
+
className: "rounded-2xl border border-border-seam bg-bg-surface p-6",
|
|
88
|
+
children: [/* @__PURE__ */ x("div", {
|
|
89
|
+
className: "flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between",
|
|
90
|
+
children: [/* @__PURE__ */ x("div", {
|
|
91
|
+
className: "flex flex-col items-center gap-4 sm:flex-row sm:items-start",
|
|
92
|
+
children: [/* @__PURE__ */ b(o, {
|
|
93
|
+
name: D.displayName,
|
|
94
|
+
presence: D.presence,
|
|
95
|
+
size: "xl",
|
|
96
|
+
fileId: D.avatar?.id
|
|
97
|
+
}), /* @__PURE__ */ x("div", {
|
|
98
|
+
className: "min-w-0 text-center sm:text-left",
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ b("h2", {
|
|
101
|
+
className: "text-2xl font-semibold text-text-primary",
|
|
102
|
+
children: D.displayName
|
|
103
|
+
}),
|
|
104
|
+
D.headline && /* @__PURE__ */ b("p", {
|
|
105
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
106
|
+
children: D.headline
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ b("div", {
|
|
109
|
+
className: "mt-3 flex justify-center sm:justify-start",
|
|
110
|
+
children: /* @__PURE__ */ b(p, { status: D.presence })
|
|
111
|
+
})
|
|
112
|
+
]
|
|
113
|
+
})]
|
|
114
|
+
}), /* @__PURE__ */ b("div", {
|
|
115
|
+
className: "flex shrink-0 justify-center sm:justify-end",
|
|
116
|
+
children: z
|
|
117
|
+
})]
|
|
118
|
+
}), D.bio && /* @__PURE__ */ b("p", {
|
|
119
|
+
className: "mt-5 whitespace-pre-wrap text-sm text-text-secondary",
|
|
120
|
+
children: D.bio
|
|
121
|
+
})]
|
|
122
|
+
}),
|
|
123
|
+
D.interests.length > 0 && /* @__PURE__ */ b(f, {
|
|
124
|
+
title: "Interests",
|
|
125
|
+
children: /* @__PURE__ */ b(C, { items: D.interests })
|
|
126
|
+
}),
|
|
127
|
+
D.skills.length > 0 && /* @__PURE__ */ b(f, {
|
|
128
|
+
title: "Skills",
|
|
129
|
+
children: /* @__PURE__ */ b(C, { items: D.skills })
|
|
130
|
+
})
|
|
131
|
+
]
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { w as MemberProfilePage };
|
|
137
|
+
|
|
138
|
+
//# sourceMappingURL=MemberProfilePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MemberProfilePage.js","names":[],"sources":["../../../../src/comradecircle/pages/members/MemberProfilePage.tsx"],"sourcesContent":["/**\n * MemberProfilePage — a single member's public social profile.\n * @module comradecircle/pages/members\n *\n * Reads `comProfile(memberId)` (the social-identity layer). The visitor can\n * follow / unfollow the member (state derived from `comMyFollows('MEMBER')`);\n * on their own profile the action becomes an \"Edit profile\" shortcut. Interests\n * and skills render as token-only chips and hide when empty.\n */\n\nimport { ArrowLeft, UserX, UserPlus, UserCheck, Pencil } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { useComProfile, useComMyFollows, useComFollow, useComUnfollow } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { notify, describeError } from '../../lib/toast';\nimport { useParams } from 'react-router-dom';\n\nfunction ChipList({ items }: { items: string[] }) {\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {items.map((item) => (\n <span\n key={item}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {item}\n </span>\n ))}\n </div>\n );\n}\n\nexport function MemberProfilePage() {\n const { memberId } = useParams<{ memberId: string }>();\n const { navigateTo, currentUser } = useComradeCircle();\n const { data: profile, isLoading, isError, refetch } = useComProfile(memberId);\n\n const follows = useComMyFollows('MEMBER');\n const follow = useComFollow();\n const unfollow = useComUnfollow();\n\n const back = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('members')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <LoadingView label=\"Loading profile…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <ErrorView message=\"We couldn't load this member's profile.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n if (!profile) {\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <EmptyState\n icon={<UserX className=\"h-10 w-10\" />}\n title=\"No profile\"\n description=\"This member hasn't set up a ComradeCircle profile yet.\"\n />\n </PageLayout>\n );\n }\n\n const isSelf = Boolean(memberId && currentUser?.id === memberId);\n const isFollowing = (follows.data ?? []).some(\n (f) => f.targetId === memberId && f.targetType === 'MEMBER'\n );\n const followPending = follow.isPending || unfollow.isPending;\n\n const toggleFollow = async () => {\n if (!memberId) return;\n try {\n if (isFollowing) {\n await unfollow.mutateAsync({ targetId: memberId, targetType: 'MEMBER' });\n notify.success('Unfollowed', profile.displayName);\n } else {\n await follow.mutateAsync({ targetId: memberId, targetType: 'MEMBER' });\n notify.success('Following', profile.displayName);\n }\n } catch (err) {\n notify.error('Could not update follow', describeError(err));\n }\n };\n\n const action = isSelf ? (\n <Button variant=\"outline\" onClick={() => navigateTo('profile')}>\n <Pencil className=\"mr-1.5 h-4 w-4\" /> Edit profile\n </Button>\n ) : isFollowing ? (\n <Button variant=\"outline\" onClick={toggleFollow} disabled={followPending}>\n <UserCheck className=\"mr-1.5 h-4 w-4\" /> Following\n </Button>\n ) : (\n <Button onClick={toggleFollow} disabled={followPending}>\n <UserPlus className=\"mr-1.5 h-4 w-4\" /> Follow\n </Button>\n );\n\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <div className=\"mx-auto flex max-w-2xl flex-col gap-6\">\n {/* Identity header */}\n <div className=\"rounded-2xl border border-border-seam bg-bg-surface p-6\">\n <div className=\"flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex flex-col items-center gap-4 sm:flex-row sm:items-start\">\n <Avatar\n name={profile.displayName}\n presence={profile.presence}\n size=\"xl\"\n fileId={profile.avatar?.id}\n />\n <div className=\"min-w-0 text-center sm:text-left\">\n <h2 className=\"text-2xl font-semibold text-text-primary\">{profile.displayName}</h2>\n {profile.headline && (\n <p className=\"mt-1 text-sm text-text-secondary\">{profile.headline}</p>\n )}\n <div className=\"mt-3 flex justify-center sm:justify-start\">\n <StatusBadge status={profile.presence} />\n </div>\n </div>\n </div>\n <div className=\"flex shrink-0 justify-center sm:justify-end\">{action}</div>\n </div>\n\n {profile.bio && (\n <p className=\"mt-5 whitespace-pre-wrap text-sm text-text-secondary\">{profile.bio}</p>\n )}\n </div>\n\n {profile.interests.length > 0 && (\n <Section title=\"Interests\">\n <ChipList items={profile.interests} />\n </Section>\n )}\n\n {profile.skills.length > 0 && (\n <Section title=\"Skills\">\n <ChipList items={profile.skills} />\n </Section>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,SAAS,EAAS,EAAE,YAA8B;AAChD,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAM,KAAK,MACV,kBAAC,QAAD;GAEE,WAAU;aAET;GACI,EAJA,EAIA,CACP;EACE,CAAA;;AAIV,SAAgB,IAAoB;CAClC,IAAM,EAAE,gBAAa,GAAiC,EAChD,EAAE,eAAY,mBAAgB,GAAkB,EAChD,EAAE,MAAM,GAAS,cAAW,YAAS,eAAY,EAAc,EAAS,EAExE,IAAU,EAAgB,SAAS,EACnC,IAAS,GAAc,EACvB,IAAW,GAAgB,EAE3B,IACJ,kBAAC,GAAD;EAAQ,SAAQ;EAAQ,MAAK;EAAK,eAAe,EAAW,UAAU;YAAtE,CACE,kBAAC,GAAD,EAAW,WAAU,kBAAmB,CAAA,EAAA,QACjC;;AAGX,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;EAAU,SAAS;YACnC,kBAAC,GAAD,EAAa,OAAM,oBAAqB,CAAA;EAC7B,CAAA;AAIjB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;EAAU,SAAS;YACnC,kBAAC,GAAD;GAAW,SAAQ;GAA0C,eAAe,GAAS;GAAI,CAAA;EAC9E,CAAA;AAIjB,KAAI,CAAC,EACH,QACE,kBAAC,GAAD;EAAY,OAAM;EAAU,SAAS;YACnC,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;GACrC,OAAM;GACN,aAAY;GACZ,CAAA;EACS,CAAA;CAIjB,IAAM,IAAS,GAAQ,KAAY,GAAa,OAAO,IACjD,KAAe,EAAQ,QAAQ,EAAE,EAAE,MACtC,MAAM,EAAE,aAAa,KAAY,EAAE,eAAe,SACpD,EACK,IAAgB,EAAO,aAAa,EAAS,WAE7C,IAAe,YAAY;AAC1B,QACL,KAAI;AACF,GAAI,KACF,MAAM,EAAS,YAAY;IAAE,UAAU;IAAU,YAAY;IAAU,CAAC,EACxE,EAAO,QAAQ,cAAc,EAAQ,YAAY,KAEjD,MAAM,EAAO,YAAY;IAAE,UAAU;IAAU,YAAY;IAAU,CAAC,EACtE,EAAO,QAAQ,aAAa,EAAQ,YAAY;WAE3C,GAAK;AACZ,KAAO,MAAM,2BAA2B,EAAc,EAAI,CAAC;;IAIzD,IAAS,IACb,kBAAC,GAAD;EAAQ,SAAQ;EAAU,eAAe,EAAW,UAAU;YAA9D,CACE,kBAAC,GAAD,EAAQ,WAAU,kBAAmB,CAAA,EAAA,gBAC9B;MACP,IACF,kBAAC,GAAD;EAAQ,SAAQ;EAAU,SAAS;EAAc,UAAU;YAA3D,CACE,kBAAC,GAAD,EAAW,WAAU,kBAAmB,CAAA,EAAA,aACjC;MAET,kBAAC,GAAD;EAAQ,SAAS;EAAc,UAAU;YAAzC,CACE,kBAAC,GAAD,EAAU,WAAU,kBAAmB,CAAA,EAAA,UAChC;;AAGX,QACE,kBAAC,GAAD;EAAY,OAAM;EAAU,SAAS;YACnC,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,MAAM,EAAQ;QACd,UAAU,EAAQ;QAClB,MAAK;QACL,QAAQ,EAAQ,QAAQ;QACxB,CAAA,EACF,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,MAAD;UAAI,WAAU;oBAA4C,EAAQ;UAAiB,CAAA;SAClF,EAAQ,YACP,kBAAC,KAAD;UAAG,WAAU;oBAAoC,EAAQ;UAAa,CAAA;SAExE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD,EAAa,QAAQ,EAAQ,UAAY,CAAA;UACrC,CAAA;SACF;UACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBAA+C;OAAa,CAAA,CACvE;SAEL,EAAQ,OACP,kBAAC,KAAD;MAAG,WAAU;gBAAwD,EAAQ;MAAQ,CAAA,CAEnF;;IAEL,EAAQ,UAAU,SAAS,KAC1B,kBAAC,GAAD;KAAS,OAAM;eACb,kBAAC,GAAD,EAAU,OAAO,EAAQ,WAAa,CAAA;KAC9B,CAAA;IAGX,EAAQ,OAAO,SAAS,KACvB,kBAAC,GAAD;KAAS,OAAM;eACb,kBAAC,GAAD,EAAU,OAAO,EAAQ,QAAU,CAAA;KAC3B,CAAA;IAER;;EACK,CAAA"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import "../../context/index.js";
|
|
4
|
+
import { useComFollow as n, useComMyFollows as r, useComProfiles as i, useComUnfollow as a } from "../../hooks/useComradeCircleApi.js";
|
|
5
|
+
import { useCursorPager as o } from "../../hooks/useCursorPager.js";
|
|
6
|
+
import "../../hooks/index.js";
|
|
7
|
+
import { Avatar as s } from "../../components/Avatar.js";
|
|
8
|
+
import { describeError as c, notify as l } from "../../lib/toast.js";
|
|
9
|
+
import { EmptyState as u } from "../../components/EmptyState.js";
|
|
10
|
+
import { ErrorView as d, LoadingView as f } from "../../components/StateViews.js";
|
|
11
|
+
import { Pagination as p } from "../../components/Pagination.js";
|
|
12
|
+
import { StatusBadge as m } from "../../components/StatusBadge.js";
|
|
13
|
+
import { useMemo as h, useState as g } from "react";
|
|
14
|
+
import { Check as _, Search as v, UserPlus as y } from "lucide-react";
|
|
15
|
+
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
16
|
+
import { Button as S, GlassCard as C, Input as w } from "@burdenoff/fe-libs/ui";
|
|
17
|
+
//#region src/comradecircle/pages/members/MembersPage.tsx
|
|
18
|
+
var T = 12;
|
|
19
|
+
function E() {
|
|
20
|
+
let { navigateTo: E, currentUser: D } = e(), O = o(), [k, A] = g(""), [j, M] = g(null), { data: N, isLoading: P, isError: F, refetch: I } = i({
|
|
21
|
+
discoverable: !0,
|
|
22
|
+
pagination: {
|
|
23
|
+
limit: T,
|
|
24
|
+
cursor: O.cursor
|
|
25
|
+
}
|
|
26
|
+
}), { data: L } = r("MEMBER"), R = n(), z = a(), B = h(() => new Set((L ?? []).map((e) => e.targetId)), [L]), V = h(() => N?.items ?? [], [N]), H = h(() => {
|
|
27
|
+
let e = k.trim().toLowerCase();
|
|
28
|
+
return e ? V.filter((t) => [
|
|
29
|
+
t.displayName,
|
|
30
|
+
t.headline ?? "",
|
|
31
|
+
...t.skills,
|
|
32
|
+
...t.interests
|
|
33
|
+
].join(" ").toLowerCase().includes(e)) : V;
|
|
34
|
+
}, [V, k]), U = async (e) => {
|
|
35
|
+
let t = B.has(e.memberId), n = {
|
|
36
|
+
targetId: e.memberId,
|
|
37
|
+
targetType: "MEMBER"
|
|
38
|
+
};
|
|
39
|
+
M(e.memberId);
|
|
40
|
+
try {
|
|
41
|
+
t ? (await z.mutateAsync(n), l.success("Unfollowed", e.displayName)) : (await R.mutateAsync(n), l.success("Following", e.displayName));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
l.error(t ? "Could not unfollow" : "Could not follow", c(e));
|
|
44
|
+
} finally {
|
|
45
|
+
M(null);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return /* @__PURE__ */ b(t, {
|
|
49
|
+
title: "Members",
|
|
50
|
+
description: "Discover and follow people across your circles — search by name, headline, or skills.",
|
|
51
|
+
children: P ? /* @__PURE__ */ b(f, { label: "Loading members…" }) : F ? /* @__PURE__ */ b(d, {
|
|
52
|
+
message: "We couldn't load the member directory.",
|
|
53
|
+
onRetry: () => I()
|
|
54
|
+
}) : V.length === 0 && O.page === 0 ? /* @__PURE__ */ b(u, {
|
|
55
|
+
illustration: "empty-people",
|
|
56
|
+
title: "No members to discover yet",
|
|
57
|
+
description: "When members make their profiles discoverable, they'll show up here for you to follow."
|
|
58
|
+
}) : /* @__PURE__ */ x("div", {
|
|
59
|
+
className: "flex flex-col gap-5",
|
|
60
|
+
children: [
|
|
61
|
+
/* @__PURE__ */ x("div", {
|
|
62
|
+
className: "relative w-full max-w-sm",
|
|
63
|
+
children: [/* @__PURE__ */ b(v, {
|
|
64
|
+
className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary",
|
|
65
|
+
"aria-hidden": "true"
|
|
66
|
+
}), /* @__PURE__ */ b(w, {
|
|
67
|
+
value: k,
|
|
68
|
+
onChange: (e) => A(e.target.value),
|
|
69
|
+
placeholder: "Search by name, headline, or skills",
|
|
70
|
+
"aria-label": "Search members",
|
|
71
|
+
className: "pl-9"
|
|
72
|
+
})]
|
|
73
|
+
}),
|
|
74
|
+
H.length === 0 ? /* @__PURE__ */ b(u, {
|
|
75
|
+
icon: /* @__PURE__ */ b(v, { className: "h-9 w-9" }),
|
|
76
|
+
title: "No matches on this page",
|
|
77
|
+
description: "No members on this page match your search. Try a different term or browse another page."
|
|
78
|
+
}) : /* @__PURE__ */ b("div", {
|
|
79
|
+
className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
|
|
80
|
+
children: H.map((e) => {
|
|
81
|
+
let t = !!(D?.id && e.memberId === D.id), n = B.has(e.memberId), r = Array.from(new Set([...e.skills, ...e.interests])).slice(0, 4);
|
|
82
|
+
return /* @__PURE__ */ x(C, {
|
|
83
|
+
treatment: "plain",
|
|
84
|
+
className: "flex flex-col p-5",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ x("div", {
|
|
87
|
+
className: "flex items-start gap-3",
|
|
88
|
+
children: [/* @__PURE__ */ b(s, {
|
|
89
|
+
name: e.displayName,
|
|
90
|
+
presence: e.presence,
|
|
91
|
+
size: "lg",
|
|
92
|
+
fileId: e.avatar?.id
|
|
93
|
+
}), /* @__PURE__ */ x("div", {
|
|
94
|
+
className: "min-w-0 flex-1",
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ b("button", {
|
|
97
|
+
type: "button",
|
|
98
|
+
onClick: () => E(`members/${e.memberId}`),
|
|
99
|
+
className: "block max-w-full truncate text-left text-base font-semibold text-text-primary hover:underline",
|
|
100
|
+
children: e.displayName
|
|
101
|
+
}),
|
|
102
|
+
e.headline && /* @__PURE__ */ b("p", {
|
|
103
|
+
className: "truncate text-sm text-text-secondary",
|
|
104
|
+
children: e.headline
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ b("div", {
|
|
107
|
+
className: "mt-1.5",
|
|
108
|
+
children: /* @__PURE__ */ b(m, { status: e.presence })
|
|
109
|
+
})
|
|
110
|
+
]
|
|
111
|
+
})]
|
|
112
|
+
}),
|
|
113
|
+
r.length > 0 && /* @__PURE__ */ b("div", {
|
|
114
|
+
className: "mt-3 flex flex-wrap gap-1.5",
|
|
115
|
+
children: r.map((e) => /* @__PURE__ */ b("span", {
|
|
116
|
+
className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary",
|
|
117
|
+
children: e
|
|
118
|
+
}, e))
|
|
119
|
+
}),
|
|
120
|
+
!t && /* @__PURE__ */ b("div", {
|
|
121
|
+
className: "mt-4 flex items-center justify-end border-t border-border-seam pt-3",
|
|
122
|
+
children: n ? /* @__PURE__ */ x(S, {
|
|
123
|
+
size: "sm",
|
|
124
|
+
variant: "outline",
|
|
125
|
+
onClick: () => U(e),
|
|
126
|
+
disabled: j === e.memberId,
|
|
127
|
+
children: [/* @__PURE__ */ b(_, { className: "mr-1.5 h-4 w-4" }), " Following"]
|
|
128
|
+
}) : /* @__PURE__ */ x(S, {
|
|
129
|
+
size: "sm",
|
|
130
|
+
onClick: () => U(e),
|
|
131
|
+
disabled: j === e.memberId,
|
|
132
|
+
children: [/* @__PURE__ */ b(y, { className: "mr-1.5 h-4 w-4" }), " Follow"]
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
]
|
|
136
|
+
}, e.id);
|
|
137
|
+
})
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ b(p, {
|
|
140
|
+
page: O.page,
|
|
141
|
+
pageSize: T,
|
|
142
|
+
itemCount: V.length,
|
|
143
|
+
hasMore: N?.pageInfo.hasMore ?? !1,
|
|
144
|
+
onPageChange: (e) => O.onPageChange(e, N?.pageInfo.cursor)
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
export { E as MembersPage };
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=MembersPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MembersPage.js","names":[],"sources":["../../../../src/comradecircle/pages/members/MembersPage.tsx"],"sourcesContent":["/**\n * MembersPage — the discoverable member directory.\n * @module comradecircle/pages/members\n *\n * Reads `comListProfiles(discoverable: true)` — every member who has opted into\n * discovery via their {@link ComProfile}. Each card resolves display identity\n * from the social-identity layer (never a raw actor id), links to the member's\n * public profile, and offers a Follow/Following toggle wired to `comFollow` /\n * `comUnfollow` (targetType MEMBER). An optional client-side search narrows the\n * current page by name, headline, skills, or interests.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Search, UserPlus, Check } from 'lucide-react';\nimport { GlassCard, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useComProfiles,\n useComMyFollows,\n useComFollow,\n useComUnfollow,\n useCursorPager,\n} from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { notify, describeError } from '../../lib/toast';\nimport type { ComProfile } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nexport function MembersPage() {\n const { navigateTo, currentUser } = useComradeCircle();\n const pager = useCursorPager();\n const [search, setSearch] = useState('');\n const [pendingId, setPendingId] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useComProfiles({\n discoverable: true,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const { data: follows } = useComMyFollows('MEMBER');\n const follow = useComFollow();\n const unfollow = useComUnfollow();\n\n const followSet = useMemo(() => new Set((follows ?? []).map((f) => f.targetId)), [follows]);\n\n const profiles = useMemo(() => data?.items ?? [], [data]);\n\n const visible = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return profiles;\n return profiles.filter((p) => {\n const haystack = [p.displayName, p.headline ?? '', ...p.skills, ...p.interests]\n .join(' ')\n .toLowerCase();\n return haystack.includes(q);\n });\n }, [profiles, search]);\n\n const toggleFollow = async (profile: ComProfile) => {\n const wasFollowing = followSet.has(profile.memberId);\n const input = { targetId: profile.memberId, targetType: 'MEMBER' as const };\n setPendingId(profile.memberId);\n try {\n if (wasFollowing) {\n await unfollow.mutateAsync(input);\n notify.success('Unfollowed', profile.displayName);\n } else {\n await follow.mutateAsync(input);\n notify.success('Following', profile.displayName);\n }\n } catch (err) {\n notify.error(wasFollowing ? 'Could not unfollow' : 'Could not follow', describeError(err));\n } finally {\n setPendingId(null);\n }\n };\n\n return (\n <PageLayout\n title=\"Members\"\n description=\"Discover and follow people across your circles — search by name, headline, or skills.\"\n >\n {isLoading ? (\n <LoadingView label=\"Loading members…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load the member directory.\" onRetry={() => refetch()} />\n ) : profiles.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No members to discover yet\"\n description=\"When members make their profiles discoverable, they'll show up here for you to follow.\"\n />\n ) : (\n <div className=\"flex flex-col gap-5\">\n <div className=\"relative w-full max-w-sm\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\"\n aria-hidden=\"true\"\n />\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by name, headline, or skills\"\n aria-label=\"Search members\"\n className=\"pl-9\"\n />\n </div>\n\n {visible.length === 0 ? (\n <EmptyState\n icon={<Search className=\"h-9 w-9\" />}\n title=\"No matches on this page\"\n description=\"No members on this page match your search. Try a different term or browse another page.\"\n />\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {visible.map((p) => {\n const isSelf = Boolean(currentUser?.id && p.memberId === currentUser.id);\n const isFollowing = followSet.has(p.memberId);\n const chips = Array.from(new Set([...p.skills, ...p.interests])).slice(0, 4);\n\n return (\n <GlassCard key={p.id} treatment=\"plain\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start gap-3\">\n <Avatar\n name={p.displayName}\n presence={p.presence}\n size=\"lg\"\n fileId={p.avatar?.id}\n />\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`members/${p.memberId}`)}\n className=\"block max-w-full truncate text-left text-base font-semibold text-text-primary hover:underline\"\n >\n {p.displayName}\n </button>\n {p.headline && (\n <p className=\"truncate text-sm text-text-secondary\">{p.headline}</p>\n )}\n <div className=\"mt-1.5\">\n <StatusBadge status={p.presence} />\n </div>\n </div>\n </div>\n\n {chips.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {chips.map((chip) => (\n <span\n key={chip}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {chip}\n </span>\n ))}\n </div>\n )}\n\n {!isSelf && (\n <div className=\"mt-4 flex items-center justify-end border-t border-border-seam pt-3\">\n {isFollowing ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => toggleFollow(p)}\n disabled={pendingId === p.memberId}\n >\n <Check className=\"mr-1.5 h-4 w-4\" /> Following\n </Button>\n ) : (\n <Button\n size=\"sm\"\n onClick={() => toggleFollow(p)}\n disabled={pendingId === p.memberId}\n >\n <UserPlus className=\"mr-1.5 h-4 w-4\" /> Follow\n </Button>\n )}\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={profiles.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|