@burdenoff/microfe-comradecircle 2026.704.2 → 2026.704.3
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/components/Avatar.js +40 -17
- package/dist/comradecircle/components/Avatar.js.map +1 -1
- package/dist/comradecircle/components/AvatarUploadField.js +25 -14
- package/dist/comradecircle/components/AvatarUploadField.js.map +1 -1
- package/dist/comradecircle/components/CircleCard.js +54 -52
- package/dist/comradecircle/components/CircleCard.js.map +1 -1
- package/dist/comradecircle/components/CommentThread.js +80 -64
- package/dist/comradecircle/components/CommentThread.js.map +1 -1
- package/dist/comradecircle/components/EventCard.js +84 -58
- package/dist/comradecircle/components/EventCard.js.map +1 -1
- package/dist/comradecircle/components/MemberIdentity.js +2 -1
- package/dist/comradecircle/components/MemberIdentity.js.map +1 -1
- package/dist/comradecircle/components/PollCard.js +65 -44
- package/dist/comradecircle/components/PollCard.js.map +1 -1
- package/dist/comradecircle/components/PostCard.js +1 -1
- package/dist/comradecircle/components/PostCard.js.map +1 -1
- package/dist/comradecircle/components/ReactionBar.js +1 -1
- package/dist/comradecircle/components/ReactionBar.js.map +1 -1
- package/dist/comradecircle/components/StateViews.js +1 -16
- package/dist/comradecircle/components/StateViews.js.map +1 -1
- package/dist/comradecircle/components/StatusBadge.js +1 -1
- package/dist/comradecircle/components/StatusBadge.js.map +1 -1
- package/dist/comradecircle/hooks/useComradeCircleApi.js +1 -1
- package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
- package/dist/comradecircle/pages/FeedPage.js +79 -33
- package/dist/comradecircle/pages/FeedPage.js.map +1 -1
- package/dist/comradecircle/pages/circles/CircleDetailPage.js +354 -243
- package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/circles/CirclesPage.js +97 -47
- package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
- package/dist/comradecircle/pages/events/EventDetailPage.js +193 -126
- package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/events/EventsPage.js +74 -34
- package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
- package/dist/comradecircle/pages/members/MemberProfilePage.js +154 -82
- package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
- package/dist/comradecircle/pages/members/MembersPage.js +140 -71
- package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
- package/dist/comradecircle/pages/profile/MyProfilePage.js +298 -202
- package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,8 +4,9 @@ import "../../context/index.js";
|
|
|
4
4
|
import { useComEvents as n } from "../../hooks/useComradeCircleApi.js";
|
|
5
5
|
import { useCursorPager as r } from "../../hooks/useCursorPager.js";
|
|
6
6
|
import "../../hooks/index.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { formatNumber as i } from "../../utils/formatters.js";
|
|
8
|
+
import { EmptyState as a } from "../../components/EmptyState.js";
|
|
9
|
+
import { ErrorView as o } from "../../components/StateViews.js";
|
|
9
10
|
import { Pagination as s } from "../../components/Pagination.js";
|
|
10
11
|
import { EventCard as c } from "../../components/EventCard.js";
|
|
11
12
|
import { CreateEventDialog as l } from "../../components/CreateEventDialog.js";
|
|
@@ -13,9 +14,9 @@ import { FilterTabs as u } from "../../components/FilterTabs.js";
|
|
|
13
14
|
import { useState as d } from "react";
|
|
14
15
|
import { CalendarDays as f, Plus as p } from "lucide-react";
|
|
15
16
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
16
|
-
import { Button as g } from "@burdenoff/fe-libs/ui";
|
|
17
|
+
import { Button as g, GlassCard as _, Skeleton as v } from "@burdenoff/fe-libs/ui";
|
|
17
18
|
//#region src/comradecircle/pages/events/EventsPage.tsx
|
|
18
|
-
var
|
|
19
|
+
var y = 12, b = [
|
|
19
20
|
{
|
|
20
21
|
value: "ALL",
|
|
21
22
|
label: "All"
|
|
@@ -37,66 +38,105 @@ var _ = 12, v = [
|
|
|
37
38
|
label: "Cancelled"
|
|
38
39
|
}
|
|
39
40
|
];
|
|
40
|
-
function
|
|
41
|
-
|
|
41
|
+
function x() {
|
|
42
|
+
return /* @__PURE__ */ h(_, {
|
|
43
|
+
treatment: "plain",
|
|
44
|
+
className: "flex flex-col p-5",
|
|
45
|
+
"aria-hidden": "true",
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ h("div", {
|
|
48
|
+
className: "flex items-start justify-between gap-3",
|
|
49
|
+
children: [/* @__PURE__ */ m(v, { className: "h-5 w-20 rounded-full" }), /* @__PURE__ */ m(v, { className: "h-4 w-14" })]
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ m(v, { className: "mt-2 h-5 w-3/5" }),
|
|
52
|
+
/* @__PURE__ */ m(v, { className: "mt-2 h-3.5 w-full" }),
|
|
53
|
+
/* @__PURE__ */ m(v, { className: "mt-1.5 h-3.5 w-4/5" }),
|
|
54
|
+
/* @__PURE__ */ h("div", {
|
|
55
|
+
className: "mt-3 flex flex-col gap-2",
|
|
56
|
+
children: [/* @__PURE__ */ m(v, { className: "h-4 w-40" }), /* @__PURE__ */ m(v, { className: "h-4 w-32" })]
|
|
57
|
+
}),
|
|
58
|
+
/* @__PURE__ */ m(v, { className: "mt-3 h-1 w-full rounded-full" }),
|
|
59
|
+
/* @__PURE__ */ h("div", {
|
|
60
|
+
className: "mt-4 flex items-center justify-end gap-2 border-t border-border-seam pt-3",
|
|
61
|
+
children: [/* @__PURE__ */ m(v, { className: "h-8 w-20 rounded-md" }), /* @__PURE__ */ m(v, { className: "h-8 w-24 rounded-md" })]
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function S() {
|
|
67
|
+
return /* @__PURE__ */ m("div", {
|
|
68
|
+
className: "grid gap-4 md:grid-cols-2",
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
children: Array.from({ length: 4 }, (e, t) => /* @__PURE__ */ m(x, {}, t))
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function C() {
|
|
74
|
+
let { navigateTo: _ } = e(), v = r(), [x, C] = d("ALL"), [w, T] = d(!1), { data: E, isLoading: D, isError: O, refetch: k } = n({
|
|
42
75
|
status: x === "ALL" ? void 0 : x,
|
|
43
76
|
pagination: {
|
|
44
|
-
limit:
|
|
45
|
-
cursor:
|
|
77
|
+
limit: y,
|
|
78
|
+
cursor: v.cursor
|
|
46
79
|
}
|
|
47
|
-
}),
|
|
80
|
+
}), A = E?.items ?? [], j = E?.pageInfo.hasMore ?? !1, M = (e) => {
|
|
81
|
+
C(e), v.reset();
|
|
82
|
+
}, N = x === "ALL" ? "" : ` ${x.toLowerCase()}`, P = `${i(A.length)}${N} ${A.length === 1 ? "event" : "events"}` + (v.page > 0 ? ` · page ${v.page + 1}` : j ? " · more available" : "");
|
|
48
83
|
return /* @__PURE__ */ h(t, {
|
|
49
84
|
title: "Events",
|
|
50
85
|
description: "Upcoming and past gatherings across all the circles you belong to.",
|
|
51
86
|
actions: /* @__PURE__ */ h(g, {
|
|
52
|
-
onClick: () =>
|
|
87
|
+
onClick: () => T(!0),
|
|
53
88
|
children: [/* @__PURE__ */ m(p, { className: "mr-1.5 h-4 w-4" }), " New event"]
|
|
54
89
|
}),
|
|
55
90
|
children: [
|
|
56
91
|
/* @__PURE__ */ m(u, {
|
|
57
|
-
options:
|
|
92
|
+
options: b,
|
|
58
93
|
value: x,
|
|
59
|
-
onChange:
|
|
60
|
-
S(e), b.reset();
|
|
61
|
-
},
|
|
94
|
+
onChange: M,
|
|
62
95
|
className: "mb-5"
|
|
63
96
|
}),
|
|
64
|
-
|
|
97
|
+
D ? /* @__PURE__ */ m(S, {}) : O ? /* @__PURE__ */ m(o, {
|
|
65
98
|
message: "We couldn't load events.",
|
|
66
|
-
onRetry: () =>
|
|
67
|
-
}) :
|
|
99
|
+
onRetry: () => k()
|
|
100
|
+
}) : A.length === 0 && v.page === 0 ? /* @__PURE__ */ m(a, {
|
|
68
101
|
illustration: "empty-data",
|
|
69
102
|
icon: /* @__PURE__ */ m(f, { className: "h-10 w-10" }),
|
|
70
103
|
title: x === "ALL" ? "No events yet" : "No events match this filter",
|
|
71
104
|
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
105
|
action: /* @__PURE__ */ h(g, {
|
|
73
|
-
onClick: () =>
|
|
106
|
+
onClick: () => T(!0),
|
|
74
107
|
children: [/* @__PURE__ */ m(p, { className: "mr-1.5 h-4 w-4" }), " New event"]
|
|
75
108
|
})
|
|
76
109
|
}) : /* @__PURE__ */ h("div", {
|
|
77
110
|
className: "flex flex-col gap-4",
|
|
78
|
-
children: [
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ m("p", {
|
|
113
|
+
className: "text-xs font-medium text-text-tertiary",
|
|
114
|
+
children: P
|
|
115
|
+
}),
|
|
116
|
+
/* @__PURE__ */ m("div", {
|
|
117
|
+
className: "grid gap-4 md:grid-cols-2",
|
|
118
|
+
children: A.map((e) => /* @__PURE__ */ m(c, {
|
|
119
|
+
event: e,
|
|
120
|
+
onOpen: () => _("events/" + e.id)
|
|
121
|
+
}, e.id))
|
|
122
|
+
}),
|
|
123
|
+
/* @__PURE__ */ m(s, {
|
|
124
|
+
page: v.page,
|
|
125
|
+
pageSize: y,
|
|
126
|
+
itemCount: A.length,
|
|
127
|
+
hasMore: E?.pageInfo.hasMore ?? !1,
|
|
128
|
+
onPageChange: (e) => v.onPageChange(e, E?.pageInfo.cursor)
|
|
129
|
+
})
|
|
130
|
+
]
|
|
91
131
|
}),
|
|
92
132
|
/* @__PURE__ */ m(l, {
|
|
93
|
-
open:
|
|
94
|
-
onOpenChange:
|
|
133
|
+
open: w,
|
|
134
|
+
onOpenChange: T
|
|
95
135
|
})
|
|
96
136
|
]
|
|
97
137
|
});
|
|
98
138
|
}
|
|
99
139
|
//#endregion
|
|
100
|
-
export {
|
|
140
|
+
export { C as EventsPage };
|
|
101
141
|
|
|
102
142
|
//# sourceMappingURL=EventsPage.js.map
|
|
@@ -1 +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 {
|
|
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, GlassCard, Skeleton } 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 { ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useComEvents, useCursorPager } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatNumber } from '../../utils/formatters';\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\n/** One placeholder tile that mirrors {@link EventCard}'s glass + section rhythm. */\nfunction EventCardSkeleton() {\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col p-5\" aria-hidden=\"true\">\n <div className=\"flex items-start justify-between gap-3\">\n <Skeleton className=\"h-5 w-20 rounded-full\" />\n <Skeleton className=\"h-4 w-14\" />\n </div>\n <Skeleton className=\"mt-2 h-5 w-3/5\" />\n <Skeleton className=\"mt-2 h-3.5 w-full\" />\n <Skeleton className=\"mt-1.5 h-3.5 w-4/5\" />\n <div className=\"mt-3 flex flex-col gap-2\">\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-4 w-32\" />\n </div>\n <Skeleton className=\"mt-3 h-1 w-full rounded-full\" />\n <div className=\"mt-4 flex items-center justify-end gap-2 border-t border-border-seam pt-3\">\n <Skeleton className=\"h-8 w-20 rounded-md\" />\n <Skeleton className=\"h-8 w-24 rounded-md\" />\n </div>\n </GlassCard>\n );\n}\n\n/** Skeleton grid mirroring the live `md:grid-cols-2` EventCard layout. */\nfunction EventsSkeletonGrid() {\n return (\n <div className=\"grid gap-4 md:grid-cols-2\" aria-hidden=\"true\">\n {Array.from({ length: 4 }, (_, i) => (\n <EventCardSkeleton key={i} />\n ))}\n </div>\n );\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 const hasMore = data?.pageInfo.hasMore ?? false;\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const filterLabel = status === 'ALL' ? '' : ` ${status.toLowerCase()}`;\n const resultCaption =\n `${formatNumber(events.length)}${filterLabel} ` +\n `${events.length === 1 ? 'event' : 'events'}` +\n (pager.page > 0 ? ` · page ${pager.page + 1}` : hasMore ? ' · more available' : '');\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 <EventsSkeletonGrid />\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 <p className=\"text-xs font-medium text-text-tertiary\">{resultCaption}</p>\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":"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"}
|
|
@@ -1,138 +1,210 @@
|
|
|
1
1
|
import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
|
|
2
|
-
import {
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
3
4
|
import "../../context/index.js";
|
|
4
|
-
import { useComFollow as
|
|
5
|
+
import { useComFollow as r, useComMyFollows as i, useComProfile as a, useComUnfollow as o } from "../../hooks/useComradeCircleApi.js";
|
|
5
6
|
import "../../hooks/index.js";
|
|
6
|
-
import { Avatar as
|
|
7
|
-
import { describeError as
|
|
8
|
-
import { EmptyState as
|
|
9
|
-
import { ErrorView as
|
|
10
|
-
import { StatusBadge as
|
|
11
|
-
import { ArrowLeft as
|
|
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 } from "../../components/StateViews.js";
|
|
11
|
+
import { StatusBadge as f } from "../../components/StatusBadge.js";
|
|
12
|
+
import { ArrowLeft as p, Pencil as m, UserCheck as h, UserMinus as g, UserPlus as _, UserX as v } from "lucide-react";
|
|
12
13
|
import { useParams as y } from "react-router-dom";
|
|
13
14
|
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
14
|
-
import {
|
|
15
|
+
import { AuroraBackground as S, Button as C, GlassCard as w, GradientText as T, Skeleton as E } from "@burdenoff/fe-libs/ui";
|
|
15
16
|
//#region src/comradecircle/pages/members/MemberProfilePage.tsx
|
|
16
|
-
function
|
|
17
|
-
return /* @__PURE__ */ b("
|
|
18
|
-
className: "flex flex-wrap gap-
|
|
19
|
-
children: e.map((e) => /* @__PURE__ */ b("
|
|
20
|
-
className: "rounded-full
|
|
17
|
+
function D({ items: e, variant: n }) {
|
|
18
|
+
return /* @__PURE__ */ b("ul", {
|
|
19
|
+
className: "flex flex-wrap gap-2",
|
|
20
|
+
children: e.map((e) => /* @__PURE__ */ b("li", {
|
|
21
|
+
className: t("rounded-full px-3 py-1 text-xs font-medium motion-safe:transition-transform motion-safe:duration-150 motion-safe:hover:-translate-y-0.5", n === "interest" ? "bg-[var(--color-accent-soft)] text-accent" : "border border-border-seam text-text-primary hover:border-border-strong"),
|
|
21
22
|
children: e
|
|
22
23
|
}, e))
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
26
|
+
function O() {
|
|
27
|
+
return /* @__PURE__ */ x("div", {
|
|
28
|
+
className: "mx-auto flex max-w-2xl flex-col gap-6",
|
|
29
|
+
children: [/* @__PURE__ */ x(w, {
|
|
30
|
+
treatment: "plain",
|
|
31
|
+
className: "overflow-hidden p-0",
|
|
32
|
+
children: [/* @__PURE__ */ b(E, { className: "h-24 w-full rounded-none sm:h-28" }), /* @__PURE__ */ x("div", {
|
|
33
|
+
className: "px-5 pb-5 sm:px-6 sm:pb-6",
|
|
34
|
+
children: [/* @__PURE__ */ x("div", {
|
|
35
|
+
className: "flex flex-col items-center gap-4 sm:flex-row sm:items-end",
|
|
36
|
+
children: [/* @__PURE__ */ b(E, { className: "-mt-16 h-28 w-28 rounded-full sm:-mt-20" }), /* @__PURE__ */ x("div", {
|
|
37
|
+
className: "w-full space-y-2 pb-1 text-center sm:text-left",
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ b(E, { className: "mx-auto h-7 w-44 sm:mx-0" }),
|
|
40
|
+
/* @__PURE__ */ b(E, { className: "mx-auto h-4 w-56 sm:mx-0" }),
|
|
41
|
+
/* @__PURE__ */ b(E, { className: "mx-auto h-5 w-20 rounded-full sm:mx-0" })
|
|
42
|
+
]
|
|
43
|
+
})]
|
|
44
|
+
}), /* @__PURE__ */ x("div", {
|
|
45
|
+
className: "mt-5 space-y-2",
|
|
46
|
+
children: [/* @__PURE__ */ b(E, { className: "h-4 w-full" }), /* @__PURE__ */ b(E, { className: "h-4 w-3/4" })]
|
|
47
|
+
})]
|
|
48
|
+
})]
|
|
49
|
+
}), /* @__PURE__ */ b(w, {
|
|
50
|
+
treatment: "plain",
|
|
51
|
+
className: "flex flex-col gap-6 p-5 sm:p-6",
|
|
52
|
+
children: [0, 1].map((e) => /* @__PURE__ */ x("div", {
|
|
53
|
+
className: "space-y-3",
|
|
54
|
+
children: [/* @__PURE__ */ b(E, { className: "h-4 w-24" }), /* @__PURE__ */ b("div", {
|
|
55
|
+
className: "flex flex-wrap gap-2",
|
|
56
|
+
children: [
|
|
57
|
+
0,
|
|
58
|
+
1,
|
|
59
|
+
2,
|
|
60
|
+
3
|
|
61
|
+
].map((e) => /* @__PURE__ */ b(E, { className: "h-6 w-20 rounded-full" }, e))
|
|
62
|
+
})]
|
|
63
|
+
}, e))
|
|
64
|
+
})]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function k() {
|
|
68
|
+
let { memberId: t } = y(), { navigateTo: E, currentUser: k } = e(), { data: A, isLoading: j, isError: M, refetch: N } = a(t), P = i("MEMBER"), F = r(), I = o(), L = /* @__PURE__ */ x(C, {
|
|
27
69
|
variant: "ghost",
|
|
28
70
|
size: "sm",
|
|
29
|
-
onClick: () =>
|
|
30
|
-
children: [/* @__PURE__ */ b(
|
|
71
|
+
onClick: () => E("members"),
|
|
72
|
+
children: [/* @__PURE__ */ b(p, { className: "mr-1.5 h-4 w-4" }), " Back"]
|
|
31
73
|
});
|
|
32
|
-
if (
|
|
74
|
+
if (j) return /* @__PURE__ */ b(n, {
|
|
33
75
|
title: "Profile",
|
|
34
|
-
actions:
|
|
35
|
-
children: /* @__PURE__ */ b(
|
|
76
|
+
actions: L,
|
|
77
|
+
children: /* @__PURE__ */ b(O, {})
|
|
36
78
|
});
|
|
37
|
-
if (
|
|
79
|
+
if (M) return /* @__PURE__ */ b(n, {
|
|
38
80
|
title: "Profile",
|
|
39
|
-
actions:
|
|
40
|
-
children: /* @__PURE__ */ b(
|
|
81
|
+
actions: L,
|
|
82
|
+
children: /* @__PURE__ */ b(d, {
|
|
41
83
|
message: "We couldn't load this member's profile.",
|
|
42
|
-
onRetry: () =>
|
|
84
|
+
onRetry: () => N()
|
|
43
85
|
})
|
|
44
86
|
});
|
|
45
|
-
if (!
|
|
87
|
+
if (!A) return /* @__PURE__ */ b(n, {
|
|
46
88
|
title: "Profile",
|
|
47
|
-
actions:
|
|
48
|
-
children: /* @__PURE__ */ b(
|
|
89
|
+
actions: L,
|
|
90
|
+
children: /* @__PURE__ */ b(u, {
|
|
49
91
|
icon: /* @__PURE__ */ b(v, { className: "h-10 w-10" }),
|
|
50
92
|
title: "No profile",
|
|
51
93
|
description: "This member hasn't set up a ComradeCircle profile yet."
|
|
52
94
|
})
|
|
53
95
|
});
|
|
54
|
-
let
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
targetId:
|
|
96
|
+
let R = !!(t && k?.id === t), z = (P.data ?? []).some((e) => e.targetId === t && e.targetType === "MEMBER"), B = F.isPending || I.isPending, V = async () => {
|
|
97
|
+
if (t) try {
|
|
98
|
+
z ? (await I.mutateAsync({
|
|
99
|
+
targetId: t,
|
|
58
100
|
targetType: "MEMBER"
|
|
59
|
-
}),
|
|
60
|
-
targetId:
|
|
101
|
+
}), l.success("Unfollowed", A.displayName)) : (await F.mutateAsync({
|
|
102
|
+
targetId: t,
|
|
61
103
|
targetType: "MEMBER"
|
|
62
|
-
}),
|
|
104
|
+
}), l.success("Following", A.displayName));
|
|
63
105
|
} catch (e) {
|
|
64
|
-
|
|
106
|
+
l.error("Could not update follow", c(e));
|
|
65
107
|
}
|
|
66
|
-
},
|
|
108
|
+
}, H = R ? /* @__PURE__ */ x(C, {
|
|
67
109
|
variant: "outline",
|
|
68
|
-
onClick: () =>
|
|
69
|
-
children: [/* @__PURE__ */ b(
|
|
70
|
-
}) :
|
|
110
|
+
onClick: () => E("profile"),
|
|
111
|
+
children: [/* @__PURE__ */ b(m, { className: "mr-1.5 h-4 w-4" }), " Edit profile"]
|
|
112
|
+
}) : z ? /* @__PURE__ */ x(C, {
|
|
71
113
|
variant: "outline",
|
|
72
|
-
onClick:
|
|
73
|
-
disabled:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
114
|
+
onClick: V,
|
|
115
|
+
disabled: B,
|
|
116
|
+
"aria-label": `Following ${A.displayName}. Activate to unfollow.`,
|
|
117
|
+
className: "group/follow min-w-[8rem] justify-center motion-safe:transition-colors hover:border-status-error-border hover:bg-status-error-subtle hover:text-status-error-text",
|
|
118
|
+
children: [
|
|
119
|
+
/* @__PURE__ */ b(h, { className: "mr-1.5 h-4 w-4 group-hover/follow:hidden" }),
|
|
120
|
+
/* @__PURE__ */ b(g, { className: "mr-1.5 hidden h-4 w-4 group-hover/follow:inline" }),
|
|
121
|
+
/* @__PURE__ */ b("span", {
|
|
122
|
+
className: "group-hover/follow:hidden",
|
|
123
|
+
children: "Following"
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ b("span", {
|
|
126
|
+
className: "hidden group-hover/follow:inline",
|
|
127
|
+
children: "Unfollow"
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
}) : /* @__PURE__ */ x(C, {
|
|
131
|
+
onClick: V,
|
|
132
|
+
disabled: B,
|
|
78
133
|
children: [/* @__PURE__ */ b(_, { className: "mr-1.5 h-4 w-4" }), " Follow"]
|
|
79
|
-
});
|
|
80
|
-
return /* @__PURE__ */ b(
|
|
134
|
+
}), U = A.interests.length > 0 || A.skills.length > 0;
|
|
135
|
+
return /* @__PURE__ */ b(n, {
|
|
81
136
|
title: "Profile",
|
|
82
|
-
actions:
|
|
137
|
+
actions: L,
|
|
83
138
|
children: /* @__PURE__ */ x("div", {
|
|
84
139
|
className: "mx-auto flex max-w-2xl flex-col gap-6",
|
|
85
|
-
children: [
|
|
86
|
-
|
|
87
|
-
|
|
140
|
+
children: [/* @__PURE__ */ x(w, {
|
|
141
|
+
treatment: "plain",
|
|
142
|
+
className: "overflow-hidden p-0",
|
|
143
|
+
children: [/* @__PURE__ */ b("div", {
|
|
144
|
+
className: "relative h-24 overflow-hidden bg-gloss-card sm:h-28",
|
|
145
|
+
children: /* @__PURE__ */ b(S, { intensity: "subtle" })
|
|
146
|
+
}), /* @__PURE__ */ x("div", {
|
|
147
|
+
className: "px-5 pb-5 sm:px-6 sm:pb-6",
|
|
88
148
|
children: [/* @__PURE__ */ x("div", {
|
|
89
|
-
className: "flex flex-col gap-
|
|
149
|
+
className: "flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between",
|
|
90
150
|
children: [/* @__PURE__ */ x("div", {
|
|
91
|
-
className: "flex flex-col items-center gap-4 sm:flex-row sm:items-
|
|
92
|
-
children: [/* @__PURE__ */ b(
|
|
93
|
-
name:
|
|
94
|
-
presence:
|
|
95
|
-
size: "
|
|
96
|
-
|
|
151
|
+
className: "flex flex-col items-center gap-4 sm:flex-row sm:items-end",
|
|
152
|
+
children: [/* @__PURE__ */ b(s, {
|
|
153
|
+
name: A.displayName,
|
|
154
|
+
presence: A.presence,
|
|
155
|
+
size: "2xl",
|
|
156
|
+
seed: A.memberId,
|
|
157
|
+
fileId: A.avatar?.id,
|
|
158
|
+
ring: !0,
|
|
159
|
+
className: "-mt-16 sm:-mt-20"
|
|
97
160
|
}), /* @__PURE__ */ x("div", {
|
|
98
|
-
className: "min-w-0 text-center sm:text-left",
|
|
161
|
+
className: "min-w-0 pb-1 text-center sm:text-left",
|
|
99
162
|
children: [
|
|
100
|
-
/* @__PURE__ */ b(
|
|
101
|
-
|
|
102
|
-
|
|
163
|
+
/* @__PURE__ */ b(T, {
|
|
164
|
+
as: "h2",
|
|
165
|
+
className: "text-2xl font-semibold sm:text-[1.75rem]",
|
|
166
|
+
children: A.displayName
|
|
103
167
|
}),
|
|
104
|
-
|
|
168
|
+
A.headline && /* @__PURE__ */ b("p", {
|
|
105
169
|
className: "mt-1 text-sm text-text-secondary",
|
|
106
|
-
children:
|
|
170
|
+
children: A.headline
|
|
107
171
|
}),
|
|
108
172
|
/* @__PURE__ */ b("div", {
|
|
109
173
|
className: "mt-3 flex justify-center sm:justify-start",
|
|
110
|
-
children: /* @__PURE__ */ b(
|
|
174
|
+
children: /* @__PURE__ */ b(f, { status: A.presence })
|
|
111
175
|
})
|
|
112
176
|
]
|
|
113
177
|
})]
|
|
114
178
|
}), /* @__PURE__ */ b("div", {
|
|
115
|
-
className: "flex shrink-0 justify-center sm:justify-end",
|
|
116
|
-
children:
|
|
179
|
+
className: "flex shrink-0 justify-center pb-1 sm:justify-end",
|
|
180
|
+
children: H
|
|
117
181
|
})]
|
|
118
|
-
}),
|
|
119
|
-
className: "mt-5 whitespace-pre-wrap text-sm text-text-secondary",
|
|
120
|
-
children:
|
|
182
|
+
}), A.bio && /* @__PURE__ */ b("p", {
|
|
183
|
+
className: "mt-5 whitespace-pre-wrap text-sm leading-relaxed text-text-secondary",
|
|
184
|
+
children: A.bio
|
|
121
185
|
})]
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
186
|
+
})]
|
|
187
|
+
}), U && /* @__PURE__ */ x(w, {
|
|
188
|
+
treatment: "plain",
|
|
189
|
+
className: "flex flex-col gap-6 p-5 sm:p-6",
|
|
190
|
+
children: [A.interests.length > 0 && /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("h3", {
|
|
191
|
+
className: "mb-3 text-base font-semibold text-text-primary",
|
|
192
|
+
children: "Interests"
|
|
193
|
+
}), /* @__PURE__ */ b(D, {
|
|
194
|
+
items: A.interests,
|
|
195
|
+
variant: "interest"
|
|
196
|
+
})] }), A.skills.length > 0 && /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("h3", {
|
|
197
|
+
className: "mb-3 text-base font-semibold text-text-primary",
|
|
198
|
+
children: "Skills"
|
|
199
|
+
}), /* @__PURE__ */ b(D, {
|
|
200
|
+
items: A.skills,
|
|
201
|
+
variant: "skill"
|
|
202
|
+
})] })]
|
|
203
|
+
})]
|
|
132
204
|
})
|
|
133
205
|
});
|
|
134
206
|
}
|
|
135
207
|
//#endregion
|
|
136
|
-
export {
|
|
208
|
+
export { k as MemberProfilePage };
|
|
137
209
|
|
|
138
210
|
//# sourceMappingURL=MemberProfilePage.js.map
|
|
@@ -1 +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"}
|
|
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 differentiated chips (accent-tinted vs. outlined) and\n * hide when empty. The identity card gets a social-hero treatment: a living\n * gloss/aurora cover strip with the avatar overlapping upward onto it.\n */\n\nimport { ArrowLeft, UserX, UserMinus, UserPlus, UserCheck, Pencil } from 'lucide-react';\nimport { Button, GlassCard, GradientText, AuroraBackground, Skeleton } 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 { ErrorView } from '../../components/StateViews';\nimport { useComProfile, useComMyFollows, useComFollow, useComUnfollow } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { notify, describeError } from '../../lib/toast';\nimport { cn } from '../../utils/cn';\nimport { useParams } from 'react-router-dom';\n\n/**\n * A wrap of tag chips. `interest` chips read as accent-tinted affinities;\n * `skill` chips read as outlined competencies — a quiet visual grammar that\n * lets the two sets be told apart at a glance. Hover gives a tasteful lift.\n */\nfunction ChipList({ items, variant }: { items: string[]; variant: 'interest' | 'skill' }) {\n return (\n <ul className=\"flex flex-wrap gap-2\">\n {items.map((item) => (\n <li\n key={item}\n className={cn(\n 'rounded-full px-3 py-1 text-xs font-medium motion-safe:transition-transform motion-safe:duration-150 motion-safe:hover:-translate-y-0.5',\n variant === 'interest'\n ? 'bg-[var(--color-accent-soft)] text-accent'\n : 'border border-border-seam text-text-primary hover:border-border-strong'\n )}\n >\n {item}\n </li>\n ))}\n </ul>\n );\n}\n\n/** Profile-shaped skeleton — mirrors the hero cover strip, overlapping avatar,\n * identity block and the two chip sections so the layout doesn't jump. */\nfunction ProfileSkeleton() {\n return (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-6\">\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <Skeleton className=\"h-24 w-full rounded-none sm:h-28\" />\n <div className=\"px-5 pb-5 sm:px-6 sm:pb-6\">\n <div className=\"flex flex-col items-center gap-4 sm:flex-row sm:items-end\">\n <Skeleton className=\"-mt-16 h-28 w-28 rounded-full sm:-mt-20\" />\n <div className=\"w-full space-y-2 pb-1 text-center sm:text-left\">\n <Skeleton className=\"mx-auto h-7 w-44 sm:mx-0\" />\n <Skeleton className=\"mx-auto h-4 w-56 sm:mx-0\" />\n <Skeleton className=\"mx-auto h-5 w-20 rounded-full sm:mx-0\" />\n </div>\n </div>\n <div className=\"mt-5 space-y-2\">\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-3/4\" />\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-6 p-5 sm:p-6\">\n {[0, 1].map((s) => (\n <div key={s} className=\"space-y-3\">\n <Skeleton className=\"h-4 w-24\" />\n <div className=\"flex flex-wrap gap-2\">\n {[0, 1, 2, 3].map((c) => (\n <Skeleton key={c} className=\"h-6 w-20 rounded-full\" />\n ))}\n </div>\n </div>\n ))}\n </GlassCard>\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 <ProfileSkeleton />\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\n variant=\"outline\"\n onClick={toggleFollow}\n disabled={followPending}\n aria-label={`Following ${profile.displayName}. Activate to unfollow.`}\n className=\"group/follow min-w-[8rem] justify-center motion-safe:transition-colors hover:border-status-error-border hover:bg-status-error-subtle hover:text-status-error-text\"\n >\n <UserCheck className=\"mr-1.5 h-4 w-4 group-hover/follow:hidden\" />\n <UserMinus className=\"mr-1.5 hidden h-4 w-4 group-hover/follow:inline\" />\n <span className=\"group-hover/follow:hidden\">Following</span>\n <span className=\"hidden group-hover/follow:inline\">Unfollow</span>\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 const hasTags = profile.interests.length > 0 || profile.skills.length > 0;\n\n return (\n <PageLayout title=\"Profile\" actions={back}>\n <div className=\"mx-auto flex max-w-2xl flex-col gap-6\">\n {/* Identity hero */}\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n {/* Living cover strip — gloss + drifting aurora, reduced-motion safe */}\n <div className=\"relative h-24 overflow-hidden bg-gloss-card sm:h-28\">\n <AuroraBackground intensity=\"subtle\" />\n </div>\n\n <div className=\"px-5 pb-5 sm:px-6 sm:pb-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between\">\n <div className=\"flex flex-col items-center gap-4 sm:flex-row sm:items-end\">\n <Avatar\n name={profile.displayName}\n presence={profile.presence}\n size=\"2xl\"\n seed={profile.memberId}\n fileId={profile.avatar?.id}\n ring\n className=\"-mt-16 sm:-mt-20\"\n />\n <div className=\"min-w-0 pb-1 text-center sm:text-left\">\n <GradientText as=\"h2\" className=\"text-2xl font-semibold sm:text-[1.75rem]\">\n {profile.displayName}\n </GradientText>\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 pb-1 sm:justify-end\">{action}</div>\n </div>\n\n {profile.bio && (\n <p className=\"mt-5 whitespace-pre-wrap text-sm leading-relaxed text-text-secondary\">\n {profile.bio}\n </p>\n )}\n </div>\n </GlassCard>\n\n {/* Interests & skills — differentiated chip grammar */}\n {hasTags && (\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-6 p-5 sm:p-6\">\n {profile.interests.length > 0 && (\n <div>\n <h3 className=\"mb-3 text-base font-semibold text-text-primary\">Interests</h3>\n <ChipList items={profile.interests} variant=\"interest\" />\n </div>\n )}\n {profile.skills.length > 0 && (\n <div>\n <h3 className=\"mb-3 text-base font-semibold text-text-primary\">Skills</h3>\n <ChipList items={profile.skills} variant=\"skill\" />\n </div>\n )}\n </GlassCard>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|