@burdenoff/microfe-comradecircle 2026.720.3 → 2026.730.1
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/hooks/useComSubscription.d.ts +18 -0
- package/dist/comradecircle/hooks/useComSubscription.d.ts.map +1 -0
- package/dist/comradecircle/hooks/useComSubscription.js +110 -0
- package/dist/comradecircle/hooks/useComSubscription.js.map +1 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.d.ts +23 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.d.ts.map +1 -1
- package/dist/comradecircle/hooks/useComradeCircleApi.js +148 -99
- package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
- package/dist/comradecircle/pages/circles/CircleDetailPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/circles/CircleDetailPage.js +268 -266
- package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/events/EventDetailPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/events/EventDetailPage.js +84 -82
- package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/posts/PostThreadPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/posts/PostThreadPage.js +26 -24
- package/dist/comradecircle/pages/posts/PostThreadPage.js.map +1 -1
- package/dist/comradecircle/utils/shellBridge.d.ts +45 -0
- package/dist/comradecircle/utils/shellBridge.d.ts.map +1 -0
- package/dist/comradecircle/utils/shellBridge.js +19 -0
- package/dist/comradecircle/utils/shellBridge.js.map +1 -0
- package/package.json +1 -1
|
@@ -2,21 +2,21 @@ import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
|
|
|
2
2
|
import "../../context/index.js";
|
|
3
3
|
import { cn as t } from "../../utils/cn.js";
|
|
4
4
|
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
5
|
-
import { useComEvent as ee, useComRsvpEvent as te, useComTransitionEvent as
|
|
5
|
+
import { useComEvent as r, useComEventLive as ee, useComRsvpEvent as te, useComTransitionEvent as i } from "../../hooks/useComradeCircleApi.js";
|
|
6
6
|
import "../../hooks/index.js";
|
|
7
|
-
import { MemberIdentity as
|
|
8
|
-
import { describeError as
|
|
9
|
-
import { formatDateTime as
|
|
10
|
-
import { EmptyState as
|
|
11
|
-
import { ErrorView as
|
|
12
|
-
import { StatusBadge as
|
|
13
|
-
import { MiniBar as
|
|
14
|
-
import { ArrowLeft as
|
|
15
|
-
import { useParams as
|
|
16
|
-
import { Fragment as
|
|
17
|
-
import { Button as E, GlassCard as
|
|
7
|
+
import { MemberIdentity as a } from "../../components/MemberIdentity.js";
|
|
8
|
+
import { describeError as o, notify as s } from "../../lib/toast.js";
|
|
9
|
+
import { formatDateTime as c, formatEnumLabel as l, formatNumber as u } from "../../utils/formatters.js";
|
|
10
|
+
import { EmptyState as d } from "../../components/EmptyState.js";
|
|
11
|
+
import { ErrorView as f, LoadingView as p } from "../../components/StateViews.js";
|
|
12
|
+
import { StatusBadge as m } from "../../components/StatusBadge.js";
|
|
13
|
+
import { MiniBar as h } from "../../components/MiniBar.js";
|
|
14
|
+
import { ArrowLeft as ne, Ban as re, CalendarDays as ie, Check as g, CheckCircle2 as _, MapPin as v, Radio as y, Tag as ae, Users as b, Video as x } from "lucide-react";
|
|
15
|
+
import { useParams as S } from "react-router-dom";
|
|
16
|
+
import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
17
|
+
import { Button as E, GlassCard as oe } from "@burdenoff/fe-libs/ui";
|
|
18
18
|
//#region src/comradecircle/pages/events/EventDetailPage.tsx
|
|
19
|
-
var
|
|
19
|
+
var D = [
|
|
20
20
|
{
|
|
21
21
|
status: "GOING",
|
|
22
22
|
label: "Going"
|
|
@@ -33,12 +33,12 @@ var oe = [
|
|
|
33
33
|
status: "NOT_GOING",
|
|
34
34
|
label: "Can't go"
|
|
35
35
|
}
|
|
36
|
-
],
|
|
36
|
+
], O = {
|
|
37
37
|
GOING: 0,
|
|
38
38
|
INTERESTED: 1,
|
|
39
39
|
WAITLISTED: 2,
|
|
40
40
|
NOT_GOING: 3
|
|
41
|
-
},
|
|
41
|
+
}, k = [
|
|
42
42
|
{
|
|
43
43
|
status: "GOING",
|
|
44
44
|
label: "going"
|
|
@@ -56,110 +56,112 @@ var oe = [
|
|
|
56
56
|
label: "can't go"
|
|
57
57
|
}
|
|
58
58
|
];
|
|
59
|
-
function
|
|
60
|
-
let { id:
|
|
59
|
+
function A() {
|
|
60
|
+
let { id: A } = S(), { currentUser: j, navigateTo: M } = e(), { data: N, isLoading: P, isError: F, refetch: I } = r(A);
|
|
61
|
+
ee(A);
|
|
62
|
+
let L = te(), R = i(), z = /* @__PURE__ */ T(E, {
|
|
61
63
|
variant: "ghost",
|
|
62
64
|
size: "sm",
|
|
63
|
-
onClick: () =>
|
|
64
|
-
children: [/* @__PURE__ */ w(
|
|
65
|
+
onClick: () => M("events"),
|
|
66
|
+
children: [/* @__PURE__ */ w(ne, { className: "mr-1.5 h-4 w-4" }), " Back"]
|
|
65
67
|
});
|
|
66
|
-
if (
|
|
68
|
+
if (P) return /* @__PURE__ */ w(n, {
|
|
67
69
|
title: "Event",
|
|
68
|
-
actions:
|
|
69
|
-
children: /* @__PURE__ */ w(
|
|
70
|
+
actions: z,
|
|
71
|
+
children: /* @__PURE__ */ w(p, { label: "Loading event…" })
|
|
70
72
|
});
|
|
71
|
-
if (
|
|
73
|
+
if (F || !N) return /* @__PURE__ */ w(n, {
|
|
72
74
|
title: "Event",
|
|
73
|
-
actions:
|
|
74
|
-
children: /* @__PURE__ */ w(
|
|
75
|
+
actions: z,
|
|
76
|
+
children: /* @__PURE__ */ w(f, {
|
|
75
77
|
title: "Event not found",
|
|
76
78
|
message: "This event may have been cancelled, or you may not have access to it.",
|
|
77
|
-
onRetry: () =>
|
|
79
|
+
onRetry: () => I()
|
|
78
80
|
})
|
|
79
81
|
});
|
|
80
|
-
let
|
|
82
|
+
let B = !!(j?.id && j.id === N.hostId), V = N.status === "LIVE", se = N.status === "SCHEDULED" || N.status === "LIVE", H = N.mode === "ONLINE" || N.mode === "HYBRID", U = N.mode === "IN_PERSON" || N.mode === "HYBRID", W = V && H && !!N.joinUrl, G = j?.id ? N.rsvps.find((e) => e.memberId === j.id) : void 0, K = N.capacity ?? null, q = K && K > 0 ? Math.min(100, N.rsvpCount / K * 100) : null, J = q != null && q >= 80, Y = q != null && q >= 100, X = q == null ? "" : Y ? "Full" : `${Math.round(q)}% full`, ce = Y ? "bg-status-error-text" : J ? "bg-status-warning-text" : "bg-accent", le = Y ? "text-status-error-text" : J ? "text-status-warning-text" : "text-text-tertiary", Z = N.rsvps.reduce((e, t) => (e[t.status] += 1, e), {
|
|
81
83
|
GOING: 0,
|
|
82
84
|
INTERESTED: 0,
|
|
83
85
|
WAITLISTED: 0,
|
|
84
86
|
NOT_GOING: 0
|
|
85
|
-
}),
|
|
87
|
+
}), ue = [...N.rsvps].sort((e, t) => O[e.status] - O[t.status]), Q = k.filter((e) => Z[e.status] > 0).map((e) => `${u(Z[e.status])} ${e.label}`).join(" · "), de = async (e) => {
|
|
86
88
|
try {
|
|
87
|
-
await
|
|
88
|
-
eventId:
|
|
89
|
+
await L.mutateAsync({
|
|
90
|
+
eventId: N.id,
|
|
89
91
|
status: e
|
|
90
|
-
}),
|
|
92
|
+
}), s.success("RSVP updated", `${l(e)} · ${N.title}`);
|
|
91
93
|
} catch (e) {
|
|
92
|
-
|
|
94
|
+
s.error("Could not RSVP", o(e));
|
|
93
95
|
}
|
|
94
96
|
}, $ = async (e, t) => {
|
|
95
97
|
try {
|
|
96
|
-
await
|
|
97
|
-
id:
|
|
98
|
+
await R.mutateAsync({
|
|
99
|
+
id: N.id,
|
|
98
100
|
status: e
|
|
99
|
-
}),
|
|
101
|
+
}), s.success(t, N.title);
|
|
100
102
|
} catch (e) {
|
|
101
|
-
|
|
103
|
+
s.error("Could not update the event", o(e));
|
|
102
104
|
}
|
|
103
|
-
},
|
|
105
|
+
}, fe = N.endsAt ? `${c(N.startsAt)} – ${c(N.endsAt)}` : c(N.startsAt);
|
|
104
106
|
return /* @__PURE__ */ w(n, {
|
|
105
|
-
title:
|
|
106
|
-
actions:
|
|
107
|
+
title: N.title,
|
|
108
|
+
actions: z,
|
|
107
109
|
children: /* @__PURE__ */ T("div", {
|
|
108
110
|
className: "mx-auto flex max-w-3xl flex-col gap-6",
|
|
109
111
|
children: [
|
|
110
|
-
/* @__PURE__ */ T(
|
|
112
|
+
/* @__PURE__ */ T(oe, {
|
|
111
113
|
treatment: "plain",
|
|
112
|
-
className: t("flex flex-col gap-5 p-5 sm:p-6",
|
|
114
|
+
className: t("flex flex-col gap-5 p-5 sm:p-6", V && "ring-1 ring-[var(--color-accent-soft)]"),
|
|
113
115
|
children: [
|
|
114
|
-
|
|
116
|
+
V && /* @__PURE__ */ w("span", {
|
|
115
117
|
"aria-hidden": "true",
|
|
116
118
|
className: "pointer-events-none absolute inset-0 rounded-card ring-2 ring-accent/60 motion-safe:animate-pulse"
|
|
117
119
|
}),
|
|
118
120
|
/* @__PURE__ */ T("div", {
|
|
119
121
|
className: "flex flex-wrap items-center gap-2",
|
|
120
|
-
children: [/* @__PURE__ */ w(
|
|
122
|
+
children: [/* @__PURE__ */ w(m, { status: N.status }), /* @__PURE__ */ w(m, { status: N.mode })]
|
|
121
123
|
}),
|
|
122
|
-
W &&
|
|
124
|
+
W && N.joinUrl && /* @__PURE__ */ w(E, {
|
|
123
125
|
asChild: !0,
|
|
124
126
|
size: "lg",
|
|
125
127
|
className: "w-full justify-center sm:w-auto sm:self-start",
|
|
126
128
|
children: /* @__PURE__ */ T("a", {
|
|
127
|
-
href:
|
|
129
|
+
href: N.joinUrl,
|
|
128
130
|
target: "_blank",
|
|
129
131
|
rel: "noreferrer noopener",
|
|
130
|
-
children: [/* @__PURE__ */ w(
|
|
132
|
+
children: [/* @__PURE__ */ w(x, { className: "h-4 w-4" }), " Join the live event"]
|
|
131
133
|
})
|
|
132
134
|
}),
|
|
133
|
-
|
|
135
|
+
N.description && /* @__PURE__ */ w("p", {
|
|
134
136
|
className: "whitespace-pre-wrap text-sm text-text-secondary",
|
|
135
|
-
children:
|
|
137
|
+
children: N.description
|
|
136
138
|
}),
|
|
137
139
|
/* @__PURE__ */ T("dl", {
|
|
138
140
|
className: "flex flex-col gap-3 text-sm",
|
|
139
141
|
children: [
|
|
140
142
|
/* @__PURE__ */ T("div", {
|
|
141
143
|
className: "flex items-start gap-2.5",
|
|
142
|
-
children: [/* @__PURE__ */ w(
|
|
144
|
+
children: [/* @__PURE__ */ w(ie, { className: "mt-0.5 h-4 w-4 shrink-0 text-text-tertiary" }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("dt", {
|
|
143
145
|
className: "text-text-tertiary",
|
|
144
146
|
children: "When"
|
|
145
147
|
}), /* @__PURE__ */ w("dd", {
|
|
146
148
|
className: "mt-0.5 text-text-primary",
|
|
147
|
-
children:
|
|
149
|
+
children: fe
|
|
148
150
|
})] })]
|
|
149
151
|
}),
|
|
150
|
-
U &&
|
|
152
|
+
U && N.location && /* @__PURE__ */ T("div", {
|
|
151
153
|
className: "flex items-start gap-2.5",
|
|
152
154
|
children: [/* @__PURE__ */ w(v, { className: "mt-0.5 h-4 w-4 shrink-0 text-text-tertiary" }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("dt", {
|
|
153
155
|
className: "text-text-tertiary",
|
|
154
156
|
children: "Where"
|
|
155
157
|
}), /* @__PURE__ */ w("dd", {
|
|
156
158
|
className: "mt-0.5 text-text-primary",
|
|
157
|
-
children:
|
|
159
|
+
children: N.location
|
|
158
160
|
})] })]
|
|
159
161
|
}),
|
|
160
162
|
H && /* @__PURE__ */ T("div", {
|
|
161
163
|
className: "flex items-start gap-2.5",
|
|
162
|
-
children: [/* @__PURE__ */ w(
|
|
164
|
+
children: [/* @__PURE__ */ w(x, { className: "mt-0.5 h-4 w-4 shrink-0 text-text-tertiary" }), /* @__PURE__ */ T("div", {
|
|
163
165
|
className: "min-w-0",
|
|
164
166
|
children: [/* @__PURE__ */ w("dt", {
|
|
165
167
|
className: "text-text-tertiary",
|
|
@@ -171,14 +173,14 @@ function k() {
|
|
|
171
173
|
children: "The join link is live — use the button above."
|
|
172
174
|
}) : /* @__PURE__ */ w("span", {
|
|
173
175
|
className: "text-text-secondary",
|
|
174
|
-
children:
|
|
176
|
+
children: V ? "A join link has not been provided." : "The join link becomes available when the event goes live."
|
|
175
177
|
})
|
|
176
178
|
})]
|
|
177
179
|
})]
|
|
178
180
|
}),
|
|
179
181
|
/* @__PURE__ */ T("div", {
|
|
180
182
|
className: "flex items-start gap-2.5",
|
|
181
|
-
children: [/* @__PURE__ */ w(
|
|
183
|
+
children: [/* @__PURE__ */ w(b, { className: "mt-0.5 h-4 w-4 shrink-0 text-text-tertiary" }), /* @__PURE__ */ T("div", {
|
|
182
184
|
className: "min-w-0 flex-1",
|
|
183
185
|
children: [
|
|
184
186
|
/* @__PURE__ */ w("dt", {
|
|
@@ -187,14 +189,14 @@ function k() {
|
|
|
187
189
|
}),
|
|
188
190
|
/* @__PURE__ */ T("dd", {
|
|
189
191
|
className: "mt-0.5 flex flex-wrap items-baseline gap-x-2 text-text-primary",
|
|
190
|
-
children: [/* @__PURE__ */ T("span", { children: [
|
|
191
|
-
className: t("text-xs font-semibold",
|
|
192
|
+
children: [/* @__PURE__ */ T("span", { children: [u(N.rsvpCount), K ? ` / ${u(K)}` : ""] }), X && /* @__PURE__ */ w("span", {
|
|
193
|
+
className: t("text-xs font-semibold", le),
|
|
192
194
|
children: X
|
|
193
195
|
})]
|
|
194
196
|
}),
|
|
195
|
-
q != null && /* @__PURE__ */ w(
|
|
197
|
+
q != null && /* @__PURE__ */ w(h, {
|
|
196
198
|
value: q,
|
|
197
|
-
barClassName:
|
|
199
|
+
barClassName: ce,
|
|
198
200
|
className: "mt-2 max-w-xs"
|
|
199
201
|
})
|
|
200
202
|
]
|
|
@@ -202,12 +204,12 @@ function k() {
|
|
|
202
204
|
})
|
|
203
205
|
]
|
|
204
206
|
}),
|
|
205
|
-
|
|
207
|
+
N.tags.length > 0 && /* @__PURE__ */ T("div", {
|
|
206
208
|
className: "flex flex-wrap items-center gap-1.5",
|
|
207
|
-
children: [/* @__PURE__ */ w(
|
|
209
|
+
children: [/* @__PURE__ */ w(ae, {
|
|
208
210
|
className: "h-3.5 w-3.5 text-text-tertiary",
|
|
209
211
|
"aria-hidden": "true"
|
|
210
|
-
}),
|
|
212
|
+
}), N.tags.map((e) => /* @__PURE__ */ T("span", {
|
|
211
213
|
className: "rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
|
|
212
214
|
children: ["#", e]
|
|
213
215
|
}, e))]
|
|
@@ -217,8 +219,8 @@ function k() {
|
|
|
217
219
|
children: [/* @__PURE__ */ w("p", {
|
|
218
220
|
className: "mb-2 text-xs font-semibold uppercase tracking-wide text-text-tertiary",
|
|
219
221
|
children: "Hosted by"
|
|
220
|
-
}), /* @__PURE__ */ w(
|
|
221
|
-
memberId:
|
|
222
|
+
}), /* @__PURE__ */ w(a, {
|
|
223
|
+
memberId: N.hostId,
|
|
222
224
|
variant: "row",
|
|
223
225
|
size: "md",
|
|
224
226
|
showPresence: !0
|
|
@@ -226,7 +228,7 @@ function k() {
|
|
|
226
228
|
})
|
|
227
229
|
]
|
|
228
230
|
}),
|
|
229
|
-
|
|
231
|
+
se && /* @__PURE__ */ T("section", {
|
|
230
232
|
className: "rounded-2xl border border-border-seam bg-bg-surface p-5",
|
|
231
233
|
children: [
|
|
232
234
|
/* @__PURE__ */ w("h2", {
|
|
@@ -239,12 +241,12 @@ function k() {
|
|
|
239
241
|
}),
|
|
240
242
|
/* @__PURE__ */ w("div", {
|
|
241
243
|
className: "mt-4 flex flex-col gap-2 sm:flex-row sm:flex-wrap",
|
|
242
|
-
children:
|
|
244
|
+
children: D.map((e) => {
|
|
243
245
|
let n = G?.status === e.status;
|
|
244
246
|
return /* @__PURE__ */ T(E, {
|
|
245
247
|
variant: n ? "default" : "outline",
|
|
246
|
-
onClick: () =>
|
|
247
|
-
disabled:
|
|
248
|
+
onClick: () => de(e.status),
|
|
249
|
+
disabled: L.isPending,
|
|
248
250
|
"aria-pressed": n,
|
|
249
251
|
className: t("w-full justify-center sm:w-auto", n && "font-semibold shadow-elevation-1"),
|
|
250
252
|
children: [n && /* @__PURE__ */ w(g, { className: "h-4 w-4" }), e.label]
|
|
@@ -253,7 +255,7 @@ function k() {
|
|
|
253
255
|
})
|
|
254
256
|
]
|
|
255
257
|
}),
|
|
256
|
-
|
|
258
|
+
B && (N.status === "SCHEDULED" || N.status === "LIVE") && /* @__PURE__ */ T("section", {
|
|
257
259
|
className: "rounded-2xl border border-border-seam bg-bg-surface p-5",
|
|
258
260
|
children: [
|
|
259
261
|
/* @__PURE__ */ w("h2", {
|
|
@@ -266,21 +268,21 @@ function k() {
|
|
|
266
268
|
}),
|
|
267
269
|
/* @__PURE__ */ T("div", {
|
|
268
270
|
className: "mt-4 flex flex-wrap gap-2",
|
|
269
|
-
children: [
|
|
271
|
+
children: [N.status === "SCHEDULED" && /* @__PURE__ */ T(C, { children: [/* @__PURE__ */ T(E, {
|
|
270
272
|
size: "sm",
|
|
271
273
|
onClick: () => $("LIVE", "Event is now live"),
|
|
272
|
-
disabled:
|
|
274
|
+
disabled: R.isPending,
|
|
273
275
|
children: [/* @__PURE__ */ w(y, { className: "mr-1.5 h-4 w-4" }), " Go live"]
|
|
274
276
|
}), /* @__PURE__ */ T(E, {
|
|
275
277
|
size: "sm",
|
|
276
278
|
variant: "destructive",
|
|
277
279
|
onClick: () => $("CANCELLED", "Event cancelled"),
|
|
278
|
-
disabled:
|
|
279
|
-
children: [/* @__PURE__ */ w(
|
|
280
|
-
})] }),
|
|
280
|
+
disabled: R.isPending,
|
|
281
|
+
children: [/* @__PURE__ */ w(re, { className: "mr-1.5 h-4 w-4" }), " Cancel"]
|
|
282
|
+
})] }), N.status === "LIVE" && /* @__PURE__ */ T(E, {
|
|
281
283
|
size: "sm",
|
|
282
284
|
onClick: () => $("COMPLETED", "Event marked complete"),
|
|
283
|
-
disabled:
|
|
285
|
+
disabled: R.isPending,
|
|
284
286
|
children: [/* @__PURE__ */ w(_, { className: "mr-1.5 h-4 w-4" }), " Mark complete"]
|
|
285
287
|
})]
|
|
286
288
|
})
|
|
@@ -302,20 +304,20 @@ function k() {
|
|
|
302
304
|
}), /* @__PURE__ */ T("span", {
|
|
303
305
|
className: "inline-flex shrink-0 items-center gap-1 text-xs text-text-tertiary",
|
|
304
306
|
children: [
|
|
305
|
-
/* @__PURE__ */ w(
|
|
307
|
+
/* @__PURE__ */ w(b, { className: "h-3.5 w-3.5" }),
|
|
306
308
|
" ",
|
|
307
|
-
|
|
309
|
+
u(N.rsvps.length)
|
|
308
310
|
]
|
|
309
311
|
})]
|
|
310
|
-
}),
|
|
312
|
+
}), N.rsvps.length === 0 ? /* @__PURE__ */ w(d, {
|
|
311
313
|
illustration: "empty-people",
|
|
312
314
|
title: "No RSVPs yet",
|
|
313
315
|
description: "Be the first to let the host know you're coming."
|
|
314
316
|
}) : /* @__PURE__ */ w("ul", {
|
|
315
317
|
className: "flex flex-col gap-2",
|
|
316
|
-
children:
|
|
318
|
+
children: ue.map((e) => /* @__PURE__ */ T("li", {
|
|
317
319
|
className: "flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border-seam bg-bg-sunken px-4 py-2.5 transition-colors hover:border-[var(--color-accent-soft)]",
|
|
318
|
-
children: [/* @__PURE__ */ w(
|
|
320
|
+
children: [/* @__PURE__ */ w(a, {
|
|
319
321
|
memberId: e.memberId,
|
|
320
322
|
variant: "row",
|
|
321
323
|
size: "sm"
|
|
@@ -328,7 +330,7 @@ function k() {
|
|
|
328
330
|
e.note,
|
|
329
331
|
"”"
|
|
330
332
|
]
|
|
331
|
-
}), /* @__PURE__ */ w(
|
|
333
|
+
}), /* @__PURE__ */ w(m, { status: e.status })]
|
|
332
334
|
})]
|
|
333
335
|
}, e.id))
|
|
334
336
|
})]
|
|
@@ -338,6 +340,6 @@ function k() {
|
|
|
338
340
|
});
|
|
339
341
|
}
|
|
340
342
|
//#endregion
|
|
341
|
-
export {
|
|
343
|
+
export { A as EventDetailPage };
|
|
342
344
|
|
|
343
345
|
//# sourceMappingURL=EventDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventDetailPage.js","names":[],"sources":["../../../../src/comradecircle/pages/events/EventDetailPage.tsx"],"sourcesContent":["/**\n * EventDetailPage — a single community event.\n * @module comradecircle/pages/events\n *\n * Reads `comEvent` (with its RSVPs). Members RSVP (GOING / INTERESTED /\n * WAITLISTED / NOT_GOING) while the event is SCHEDULED or LIVE; the host drives\n * the lifecycle (SCHEDULED → LIVE → COMPLETED, or CANCELLED). Person references\n * always resolve through {@link MemberIdentity} — never a raw actor id.\n */\n\nimport {\n ArrowLeft,\n CalendarDays,\n MapPin,\n Video,\n Users,\n Radio,\n CheckCircle2,\n Check,\n Ban,\n Tag,\n} from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { MemberIdentity } from '../../components/MemberIdentity';\nimport { MiniBar } from '../../components/MiniBar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComEvent, useComRsvpEvent, useComTransitionEvent } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatDateTime, formatNumber, formatEnumLabel } from '../../utils';\nimport { cn } from '../../utils/cn';\nimport { notify, describeError } from '../../lib/toast';\nimport { useParams } from 'react-router-dom';\nimport type { ComEventStatus, ComRsvpStatus } from '../../types';\n\nconst RSVP_OPTIONS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'Going' },\n { status: 'INTERESTED', label: 'Interested' },\n { status: 'WAITLISTED', label: 'Waitlist' },\n { status: 'NOT_GOING', label: \"Can't go\" },\n];\n\n/** Attendee display order: confirmed first, declines last. */\nconst RSVP_ORDER: Record<ComRsvpStatus, number> = {\n GOING: 0,\n INTERESTED: 1,\n WAITLISTED: 2,\n NOT_GOING: 3,\n};\n\n/** Facet labels for the one-line attendee summary. */\nconst RSVP_FACETS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'going' },\n { status: 'INTERESTED', label: 'interested' },\n { status: 'WAITLISTED', label: 'waitlisted' },\n { status: 'NOT_GOING', label: \"can't go\" },\n];\n\nexport function EventDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { currentUser, navigateTo } = useComradeCircle();\n const { data: event, isLoading, isError, refetch } = useComEvent(id);\n const rsvp = useComRsvpEvent();\n const transition = useComTransitionEvent();\n\n const backButton = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('events')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <LoadingView label=\"Loading event…\" />\n </PageLayout>\n );\n }\n\n if (isError || !event) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <ErrorView\n title=\"Event not found\"\n message=\"This event may have been cancelled, or you may not have access to it.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n const isHost = Boolean(currentUser?.id && currentUser.id === event.hostId);\n const isLive = event.status === 'LIVE';\n const canRsvp = event.status === 'SCHEDULED' || event.status === 'LIVE';\n const isOnlineish = event.mode === 'ONLINE' || event.mode === 'HYBRID';\n const isInPersonish = event.mode === 'IN_PERSON' || event.mode === 'HYBRID';\n const showJoin = isLive && isOnlineish && Boolean(event.joinUrl);\n const myRsvp = currentUser?.id\n ? event.rsvps.find((r) => r.memberId === currentUser.id)\n : undefined;\n\n // Attendance / capacity — derived from real rsvpCount + capacity only.\n const capacity = event.capacity ?? null;\n const fillPct =\n capacity && capacity > 0 ? Math.min(100, (event.rsvpCount / capacity) * 100) : null;\n const nearFull = fillPct != null && fillPct >= 80;\n const isFull = fillPct != null && fillPct >= 100;\n const capacityLabel = fillPct == null ? '' : isFull ? 'Full' : `${Math.round(fillPct)}% full`;\n const capacityBar = isFull\n ? 'bg-status-error-text'\n : nearFull\n ? 'bg-status-warning-text'\n : 'bg-accent';\n const capacityTone = isFull\n ? 'text-status-error-text'\n : nearFull\n ? 'text-status-warning-text'\n : 'text-text-tertiary';\n\n // Attendees — sort GOING → INTERESTED → WAITLISTED → NOT_GOING; facet summary.\n const rsvpCounts = event.rsvps.reduce<Record<ComRsvpStatus, number>>(\n (acc, r) => {\n acc[r.status] += 1;\n return acc;\n },\n { GOING: 0, INTERESTED: 0, WAITLISTED: 0, NOT_GOING: 0 }\n );\n const sortedRsvps = [...event.rsvps].sort((a, b) => RSVP_ORDER[a.status] - RSVP_ORDER[b.status]);\n const facetSummary = RSVP_FACETS.filter((f) => rsvpCounts[f.status] > 0)\n .map((f) => `${formatNumber(rsvpCounts[f.status])} ${f.label}`)\n .join(' · ');\n\n const doRsvp = async (status: ComRsvpStatus) => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status });\n notify.success('RSVP updated', `${formatEnumLabel(status)} · ${event.title}`);\n } catch (err) {\n notify.error('Could not RSVP', describeError(err));\n }\n };\n\n const doTransition = async (status: ComEventStatus, successMessage: string) => {\n try {\n await transition.mutateAsync({ id: event.id, status });\n notify.success(successMessage, event.title);\n } catch (err) {\n notify.error('Could not update the event', describeError(err));\n }\n };\n\n const scheduleText = event.endsAt\n ? `${formatDateTime(event.startsAt)} – ${formatDateTime(event.endsAt)}`\n : formatDateTime(event.startsAt);\n\n return (\n <PageLayout title={event.title} actions={backButton}>\n <div className=\"mx-auto flex max-w-3xl flex-col gap-6\">\n {/* Overview */}\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'flex flex-col gap-5 p-5 sm:p-6',\n isLive && 'ring-1 ring-[var(--color-accent-soft)]'\n )}\n >\n {/* Motion-safe live ring — a pulsing accent halo, gated for reduced motion. */}\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 rounded-card ring-2 ring-accent/60 motion-safe:animate-pulse\"\n />\n )}\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={event.status} />\n <StatusBadge status={event.mode} />\n </div>\n\n {/* Prominent live-join CTA, promoted near the header. */}\n {showJoin && event.joinUrl && (\n <Button asChild size=\"lg\" className=\"w-full justify-center sm:w-auto sm:self-start\">\n <a href={event.joinUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n <Video className=\"h-4 w-4\" /> Join the live event\n </a>\n </Button>\n )}\n\n {event.description && (\n <p className=\"whitespace-pre-wrap text-sm text-text-secondary\">{event.description}</p>\n )}\n\n <dl className=\"flex flex-col gap-3 text-sm\">\n {/* Schedule */}\n <div className=\"flex items-start gap-2.5\">\n <CalendarDays className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">When</dt>\n <dd className=\"mt-0.5 text-text-primary\">{scheduleText}</dd>\n </div>\n </div>\n\n {/* Location (in-person / hybrid) */}\n {isInPersonish && event.location && (\n <div className=\"flex items-start gap-2.5\">\n <MapPin className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">Where</dt>\n <dd className=\"mt-0.5 text-text-primary\">{event.location}</dd>\n </div>\n </div>\n )}\n\n {/* Online join link (online / hybrid) */}\n {isOnlineish && (\n <div className=\"flex items-start gap-2.5\">\n <Video className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0\">\n <dt className=\"text-text-tertiary\">Online</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {showJoin ? (\n <span className=\"font-medium text-accent\">\n The join link is live — use the button above.\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {isLive\n ? 'A join link has not been provided.'\n : 'The join link becomes available when the event goes live.'}\n </span>\n )}\n </dd>\n </div>\n </div>\n )}\n\n {/* Attendance */}\n <div className=\"flex items-start gap-2.5\">\n <Users className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0 flex-1\">\n <dt className=\"text-text-tertiary\">Attending</dt>\n <dd className=\"mt-0.5 flex flex-wrap items-baseline gap-x-2 text-text-primary\">\n <span>\n {formatNumber(event.rsvpCount)}\n {capacity ? ` / ${formatNumber(capacity)}` : ''}\n </span>\n {capacityLabel && (\n <span className={cn('text-xs font-semibold', capacityTone)}>\n {capacityLabel}\n </span>\n )}\n </dd>\n {fillPct != null && (\n <MiniBar value={fillPct} barClassName={capacityBar} className=\"mt-2 max-w-xs\" />\n )}\n </div>\n </div>\n </dl>\n\n {/* Tags */}\n {event.tags.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Tag className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n {event.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Host */}\n <div className=\"border-t border-border-seam pt-4\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Hosted by\n </p>\n <MemberIdentity memberId={event.hostId} variant=\"row\" size=\"md\" showPresence />\n </div>\n </GlassCard>\n\n {/* RSVP controls */}\n {canRsvp && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Your RSVP</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Let the host know whether you'll attend.\n </p>\n <div className=\"mt-4 flex flex-col gap-2 sm:flex-row sm:flex-wrap\">\n {RSVP_OPTIONS.map((opt) => {\n const active = myRsvp?.status === opt.status;\n return (\n <Button\n key={opt.status}\n variant={active ? 'default' : 'outline'}\n onClick={() => doRsvp(opt.status)}\n disabled={rsvp.isPending}\n aria-pressed={active}\n className={cn(\n 'w-full justify-center sm:w-auto',\n active && 'font-semibold shadow-elevation-1'\n )}\n >\n {active && <Check className=\"h-4 w-4\" />}\n {opt.label}\n </Button>\n );\n })}\n </div>\n </section>\n )}\n\n {/* Host lifecycle controls */}\n {isHost && (event.status === 'SCHEDULED' || event.status === 'LIVE') && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Host controls</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Move this event through its lifecycle.\n </p>\n <div className=\"mt-4 flex flex-wrap gap-2\">\n {event.status === 'SCHEDULED' && (\n <>\n <Button\n size=\"sm\"\n onClick={() => doTransition('LIVE', 'Event is now live')}\n disabled={transition.isPending}\n >\n <Radio className=\"mr-1.5 h-4 w-4\" /> Go live\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => doTransition('CANCELLED', 'Event cancelled')}\n disabled={transition.isPending}\n >\n <Ban className=\"mr-1.5 h-4 w-4\" /> Cancel\n </Button>\n </>\n )}\n {event.status === 'LIVE' && (\n <Button\n size=\"sm\"\n onClick={() => doTransition('COMPLETED', 'Event marked complete')}\n disabled={transition.isPending}\n >\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" /> Mark complete\n </Button>\n )}\n </div>\n </section>\n )}\n\n {/* Attendees */}\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Attendees</h2>\n {facetSummary && (\n <p className=\"mt-0.5 truncate text-xs text-text-tertiary\">{facetSummary}</p>\n )}\n </div>\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-text-tertiary\">\n <Users className=\"h-3.5 w-3.5\" /> {formatNumber(event.rsvps.length)}\n </span>\n </div>\n {event.rsvps.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No RSVPs yet\"\n description=\"Be the first to let the host know you're coming.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-2\">\n {sortedRsvps.map((r) => (\n <li\n key={r.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border-seam bg-bg-sunken px-4 py-2.5 transition-colors hover:border-[var(--color-accent-soft)]\"\n >\n <MemberIdentity memberId={r.memberId} variant=\"row\" size=\"sm\" />\n <div className=\"flex min-w-0 items-center gap-2\">\n {r.note && (\n <span className=\"max-w-[16rem] truncate text-xs text-text-tertiary\">\n “{r.note}”\n </span>\n )}\n <StatusBadge status={r.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EventDetailPage.js","names":[],"sources":["../../../../src/comradecircle/pages/events/EventDetailPage.tsx"],"sourcesContent":["/**\n * EventDetailPage — a single community event.\n * @module comradecircle/pages/events\n *\n * Reads `comEvent` (with its RSVPs). Members RSVP (GOING / INTERESTED /\n * WAITLISTED / NOT_GOING) while the event is SCHEDULED or LIVE; the host drives\n * the lifecycle (SCHEDULED → LIVE → COMPLETED, or CANCELLED). Person references\n * always resolve through {@link MemberIdentity} — never a raw actor id.\n */\n\nimport {\n ArrowLeft,\n CalendarDays,\n MapPin,\n Video,\n Users,\n Radio,\n CheckCircle2,\n Check,\n Ban,\n Tag,\n} from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { MemberIdentity } from '../../components/MemberIdentity';\nimport { MiniBar } from '../../components/MiniBar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComEvent, useComEventLive, useComRsvpEvent, useComTransitionEvent } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatDateTime, formatNumber, formatEnumLabel } from '../../utils';\nimport { cn } from '../../utils/cn';\nimport { notify, describeError } from '../../lib/toast';\nimport { useParams } from 'react-router-dom';\nimport type { ComEventStatus, ComRsvpStatus } from '../../types';\n\nconst RSVP_OPTIONS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'Going' },\n { status: 'INTERESTED', label: 'Interested' },\n { status: 'WAITLISTED', label: 'Waitlist' },\n { status: 'NOT_GOING', label: \"Can't go\" },\n];\n\n/** Attendee display order: confirmed first, declines last. */\nconst RSVP_ORDER: Record<ComRsvpStatus, number> = {\n GOING: 0,\n INTERESTED: 1,\n WAITLISTED: 2,\n NOT_GOING: 3,\n};\n\n/** Facet labels for the one-line attendee summary. */\nconst RSVP_FACETS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'going' },\n { status: 'INTERESTED', label: 'interested' },\n { status: 'WAITLISTED', label: 'waitlisted' },\n { status: 'NOT_GOING', label: \"can't go\" },\n];\n\nexport function EventDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { currentUser, navigateTo } = useComradeCircle();\n const { data: event, isLoading, isError, refetch } = useComEvent(id);\n // BOFF-4455: live status badge + attendee count without a manual refresh.\n useComEventLive(id);\n const rsvp = useComRsvpEvent();\n const transition = useComTransitionEvent();\n\n const backButton = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('events')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <LoadingView label=\"Loading event…\" />\n </PageLayout>\n );\n }\n\n if (isError || !event) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <ErrorView\n title=\"Event not found\"\n message=\"This event may have been cancelled, or you may not have access to it.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n const isHost = Boolean(currentUser?.id && currentUser.id === event.hostId);\n const isLive = event.status === 'LIVE';\n const canRsvp = event.status === 'SCHEDULED' || event.status === 'LIVE';\n const isOnlineish = event.mode === 'ONLINE' || event.mode === 'HYBRID';\n const isInPersonish = event.mode === 'IN_PERSON' || event.mode === 'HYBRID';\n const showJoin = isLive && isOnlineish && Boolean(event.joinUrl);\n const myRsvp = currentUser?.id\n ? event.rsvps.find((r) => r.memberId === currentUser.id)\n : undefined;\n\n // Attendance / capacity — derived from real rsvpCount + capacity only.\n const capacity = event.capacity ?? null;\n const fillPct =\n capacity && capacity > 0 ? Math.min(100, (event.rsvpCount / capacity) * 100) : null;\n const nearFull = fillPct != null && fillPct >= 80;\n const isFull = fillPct != null && fillPct >= 100;\n const capacityLabel = fillPct == null ? '' : isFull ? 'Full' : `${Math.round(fillPct)}% full`;\n const capacityBar = isFull\n ? 'bg-status-error-text'\n : nearFull\n ? 'bg-status-warning-text'\n : 'bg-accent';\n const capacityTone = isFull\n ? 'text-status-error-text'\n : nearFull\n ? 'text-status-warning-text'\n : 'text-text-tertiary';\n\n // Attendees — sort GOING → INTERESTED → WAITLISTED → NOT_GOING; facet summary.\n const rsvpCounts = event.rsvps.reduce<Record<ComRsvpStatus, number>>(\n (acc, r) => {\n acc[r.status] += 1;\n return acc;\n },\n { GOING: 0, INTERESTED: 0, WAITLISTED: 0, NOT_GOING: 0 }\n );\n const sortedRsvps = [...event.rsvps].sort((a, b) => RSVP_ORDER[a.status] - RSVP_ORDER[b.status]);\n const facetSummary = RSVP_FACETS.filter((f) => rsvpCounts[f.status] > 0)\n .map((f) => `${formatNumber(rsvpCounts[f.status])} ${f.label}`)\n .join(' · ');\n\n const doRsvp = async (status: ComRsvpStatus) => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status });\n notify.success('RSVP updated', `${formatEnumLabel(status)} · ${event.title}`);\n } catch (err) {\n notify.error('Could not RSVP', describeError(err));\n }\n };\n\n const doTransition = async (status: ComEventStatus, successMessage: string) => {\n try {\n await transition.mutateAsync({ id: event.id, status });\n notify.success(successMessage, event.title);\n } catch (err) {\n notify.error('Could not update the event', describeError(err));\n }\n };\n\n const scheduleText = event.endsAt\n ? `${formatDateTime(event.startsAt)} – ${formatDateTime(event.endsAt)}`\n : formatDateTime(event.startsAt);\n\n return (\n <PageLayout title={event.title} actions={backButton}>\n <div className=\"mx-auto flex max-w-3xl flex-col gap-6\">\n {/* Overview */}\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'flex flex-col gap-5 p-5 sm:p-6',\n isLive && 'ring-1 ring-[var(--color-accent-soft)]'\n )}\n >\n {/* Motion-safe live ring — a pulsing accent halo, gated for reduced motion. */}\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 rounded-card ring-2 ring-accent/60 motion-safe:animate-pulse\"\n />\n )}\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={event.status} />\n <StatusBadge status={event.mode} />\n </div>\n\n {/* Prominent live-join CTA, promoted near the header. */}\n {showJoin && event.joinUrl && (\n <Button asChild size=\"lg\" className=\"w-full justify-center sm:w-auto sm:self-start\">\n <a href={event.joinUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n <Video className=\"h-4 w-4\" /> Join the live event\n </a>\n </Button>\n )}\n\n {event.description && (\n <p className=\"whitespace-pre-wrap text-sm text-text-secondary\">{event.description}</p>\n )}\n\n <dl className=\"flex flex-col gap-3 text-sm\">\n {/* Schedule */}\n <div className=\"flex items-start gap-2.5\">\n <CalendarDays className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">When</dt>\n <dd className=\"mt-0.5 text-text-primary\">{scheduleText}</dd>\n </div>\n </div>\n\n {/* Location (in-person / hybrid) */}\n {isInPersonish && event.location && (\n <div className=\"flex items-start gap-2.5\">\n <MapPin className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">Where</dt>\n <dd className=\"mt-0.5 text-text-primary\">{event.location}</dd>\n </div>\n </div>\n )}\n\n {/* Online join link (online / hybrid) */}\n {isOnlineish && (\n <div className=\"flex items-start gap-2.5\">\n <Video className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0\">\n <dt className=\"text-text-tertiary\">Online</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {showJoin ? (\n <span className=\"font-medium text-accent\">\n The join link is live — use the button above.\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {isLive\n ? 'A join link has not been provided.'\n : 'The join link becomes available when the event goes live.'}\n </span>\n )}\n </dd>\n </div>\n </div>\n )}\n\n {/* Attendance */}\n <div className=\"flex items-start gap-2.5\">\n <Users className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0 flex-1\">\n <dt className=\"text-text-tertiary\">Attending</dt>\n <dd className=\"mt-0.5 flex flex-wrap items-baseline gap-x-2 text-text-primary\">\n <span>\n {formatNumber(event.rsvpCount)}\n {capacity ? ` / ${formatNumber(capacity)}` : ''}\n </span>\n {capacityLabel && (\n <span className={cn('text-xs font-semibold', capacityTone)}>\n {capacityLabel}\n </span>\n )}\n </dd>\n {fillPct != null && (\n <MiniBar value={fillPct} barClassName={capacityBar} className=\"mt-2 max-w-xs\" />\n )}\n </div>\n </div>\n </dl>\n\n {/* Tags */}\n {event.tags.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Tag className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n {event.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Host */}\n <div className=\"border-t border-border-seam pt-4\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Hosted by\n </p>\n <MemberIdentity memberId={event.hostId} variant=\"row\" size=\"md\" showPresence />\n </div>\n </GlassCard>\n\n {/* RSVP controls */}\n {canRsvp && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Your RSVP</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Let the host know whether you'll attend.\n </p>\n <div className=\"mt-4 flex flex-col gap-2 sm:flex-row sm:flex-wrap\">\n {RSVP_OPTIONS.map((opt) => {\n const active = myRsvp?.status === opt.status;\n return (\n <Button\n key={opt.status}\n variant={active ? 'default' : 'outline'}\n onClick={() => doRsvp(opt.status)}\n disabled={rsvp.isPending}\n aria-pressed={active}\n className={cn(\n 'w-full justify-center sm:w-auto',\n active && 'font-semibold shadow-elevation-1'\n )}\n >\n {active && <Check className=\"h-4 w-4\" />}\n {opt.label}\n </Button>\n );\n })}\n </div>\n </section>\n )}\n\n {/* Host lifecycle controls */}\n {isHost && (event.status === 'SCHEDULED' || event.status === 'LIVE') && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Host controls</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Move this event through its lifecycle.\n </p>\n <div className=\"mt-4 flex flex-wrap gap-2\">\n {event.status === 'SCHEDULED' && (\n <>\n <Button\n size=\"sm\"\n onClick={() => doTransition('LIVE', 'Event is now live')}\n disabled={transition.isPending}\n >\n <Radio className=\"mr-1.5 h-4 w-4\" /> Go live\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => doTransition('CANCELLED', 'Event cancelled')}\n disabled={transition.isPending}\n >\n <Ban className=\"mr-1.5 h-4 w-4\" /> Cancel\n </Button>\n </>\n )}\n {event.status === 'LIVE' && (\n <Button\n size=\"sm\"\n onClick={() => doTransition('COMPLETED', 'Event marked complete')}\n disabled={transition.isPending}\n >\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" /> Mark complete\n </Button>\n )}\n </div>\n </section>\n )}\n\n {/* Attendees */}\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Attendees</h2>\n {facetSummary && (\n <p className=\"mt-0.5 truncate text-xs text-text-tertiary\">{facetSummary}</p>\n )}\n </div>\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-text-tertiary\">\n <Users className=\"h-3.5 w-3.5\" /> {formatNumber(event.rsvps.length)}\n </span>\n </div>\n {event.rsvps.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No RSVPs yet\"\n description=\"Be the first to let the host know you're coming.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-2\">\n {sortedRsvps.map((r) => (\n <li\n key={r.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border-seam bg-bg-sunken px-4 py-2.5 transition-colors hover:border-[var(--color-accent-soft)]\"\n >\n <MemberIdentity memberId={r.memberId} variant=\"row\" size=\"sm\" />\n <div className=\"flex min-w-0 items-center gap-2\">\n {r.note && (\n <span className=\"max-w-[16rem] truncate text-xs text-text-tertiary\">\n “{r.note}”\n </span>\n )}\n <StatusBadge status={r.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PostThreadPage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/posts/PostThreadPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,wBAAgB,cAAc,
|
|
1
|
+
{"version":3,"file":"PostThreadPage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/posts/PostThreadPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,wBAAgB,cAAc,4CA6C7B"}
|
|
@@ -1,41 +1,43 @@
|
|
|
1
1
|
import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
|
|
2
2
|
import "../../context/index.js";
|
|
3
3
|
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
4
|
-
import {
|
|
4
|
+
import { useComCommentAddedLive as n, useComPost as r } from "../../hooks/useComradeCircleApi.js";
|
|
5
5
|
import "../../hooks/index.js";
|
|
6
|
-
import { PostCard as
|
|
7
|
-
import { ErrorView as
|
|
8
|
-
import { ArrowLeft as
|
|
9
|
-
import { useParams as
|
|
10
|
-
import { jsx as
|
|
11
|
-
import { Button as
|
|
6
|
+
import { PostCard as i } from "../../components/PostCard.js";
|
|
7
|
+
import { ErrorView as a, LoadingView as o } from "../../components/StateViews.js";
|
|
8
|
+
import { ArrowLeft as s } from "lucide-react";
|
|
9
|
+
import { useParams as c } from "react-router-dom";
|
|
10
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
11
|
+
import { Button as d } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/comradecircle/pages/posts/PostThreadPage.tsx
|
|
13
|
-
function
|
|
14
|
-
let { postId:
|
|
13
|
+
function f() {
|
|
14
|
+
let { postId: f } = c(), { navigateTo: p } = e(), { data: m, isLoading: h, isError: g, refetch: _ } = r(f);
|
|
15
|
+
n(f);
|
|
16
|
+
let v = /* @__PURE__ */ u(d, {
|
|
15
17
|
variant: "ghost",
|
|
16
18
|
size: "sm",
|
|
17
|
-
onClick: () =>
|
|
18
|
-
children: [/* @__PURE__ */
|
|
19
|
+
onClick: () => p(""),
|
|
20
|
+
children: [/* @__PURE__ */ l(s, { className: "mr-1.5 h-4 w-4" }), " Back"]
|
|
19
21
|
});
|
|
20
|
-
return
|
|
22
|
+
return h ? /* @__PURE__ */ l(t, {
|
|
21
23
|
title: "Thread",
|
|
22
|
-
actions:
|
|
23
|
-
children: /* @__PURE__ */
|
|
24
|
-
}) :
|
|
24
|
+
actions: v,
|
|
25
|
+
children: /* @__PURE__ */ l(o, { label: "Loading post…" })
|
|
26
|
+
}) : g || !m ? /* @__PURE__ */ l(t, {
|
|
25
27
|
title: "Thread",
|
|
26
|
-
actions:
|
|
27
|
-
children: /* @__PURE__ */
|
|
28
|
+
actions: v,
|
|
29
|
+
children: /* @__PURE__ */ l(a, {
|
|
28
30
|
title: "Post not found",
|
|
29
31
|
message: "This post may have been removed, or you may not have access to it.",
|
|
30
|
-
onRetry: () =>
|
|
32
|
+
onRetry: () => _()
|
|
31
33
|
})
|
|
32
|
-
}) : /* @__PURE__ */
|
|
34
|
+
}) : /* @__PURE__ */ l(t, {
|
|
33
35
|
title: "Thread",
|
|
34
|
-
actions:
|
|
35
|
-
children: /* @__PURE__ */
|
|
36
|
+
actions: v,
|
|
37
|
+
children: /* @__PURE__ */ l("div", {
|
|
36
38
|
className: "mx-auto max-w-2xl",
|
|
37
|
-
children: /* @__PURE__ */
|
|
38
|
-
post:
|
|
39
|
+
children: /* @__PURE__ */ l(i, {
|
|
40
|
+
post: m,
|
|
39
41
|
defaultExpanded: !0,
|
|
40
42
|
hideThreadLink: !0
|
|
41
43
|
})
|
|
@@ -43,6 +45,6 @@ function d() {
|
|
|
43
45
|
});
|
|
44
46
|
}
|
|
45
47
|
//#endregion
|
|
46
|
-
export {
|
|
48
|
+
export { f as PostThreadPage };
|
|
47
49
|
|
|
48
50
|
//# sourceMappingURL=PostThreadPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PostThreadPage.js","names":[],"sources":["../../../../src/comradecircle/pages/posts/PostThreadPage.tsx"],"sourcesContent":["/**\n * PostThreadPage — a dedicated, focused view of a single post and its thread.\n * @module comradecircle/pages/posts\n *\n * BOFF-3955: the mocks (`comradecircle-14-channel-post-thread.png`) show a post\n * opened into its own surface — author, body, reactions, and comments — rather\n * than only the inline collapsible thread inside the channel feed. Reads\n * `comPost(id)` (post + its comments in one round trip) and renders the\n * existing {@link PostCard} (which already composes {@link ReactionBar} +\n * {@link CommentThread} when expanded) so the thread view stays in lock-step\n * with the inline one instead of duplicating rendering logic.\n */\n\nimport { ArrowLeft } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router-dom';\nimport { PageLayout } from '../../components/PageLayout';\nimport { PostCard } from '../../components/PostCard';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComPost } from '../../hooks';\nimport { useComradeCircle } from '../../context';\n\nexport function PostThreadPage() {\n const { postId } = useParams<{ postId: string }>();\n const { navigateTo } = useComradeCircle();\n const { data: post, isLoading, isError, refetch } = useComPost(postId);\n\n // Home feed is the one destination every thread is always reachable from\n // (channel feeds link here too), matching the fixed-destination back-button\n // convention already used by EventDetailPage / MemberProfilePage rather\n // than relying on browser history depth, which isn't reliable once this\n // MFE is mounted inside the host shell's own router.\n const backButton = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Thread\" actions={backButton}>\n <LoadingView label=\"Loading post…\" />\n </PageLayout>\n );\n }\n\n if (isError || !post) {\n return (\n <PageLayout title=\"Thread\" actions={backButton}>\n <ErrorView\n title=\"Post not found\"\n message=\"This post may have been removed, or you may not have access to it.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout title=\"Thread\" actions={backButton}>\n <div className=\"mx-auto max-w-2xl\">\n <PostCard post={post} defaultExpanded hideThreadLink />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,SAAgB,IAAiB;CAC/B,IAAM,EAAE,cAAW,EAA8B,GAC3C,EAAE,kBAAe,EAAiB,GAClC,EAAE,MAAM,GAAM,cAAW,YAAS,eAAY,EAAW,CAAM,
|
|
1
|
+
{"version":3,"file":"PostThreadPage.js","names":[],"sources":["../../../../src/comradecircle/pages/posts/PostThreadPage.tsx"],"sourcesContent":["/**\n * PostThreadPage — a dedicated, focused view of a single post and its thread.\n * @module comradecircle/pages/posts\n *\n * BOFF-3955: the mocks (`comradecircle-14-channel-post-thread.png`) show a post\n * opened into its own surface — author, body, reactions, and comments — rather\n * than only the inline collapsible thread inside the channel feed. Reads\n * `comPost(id)` (post + its comments in one round trip) and renders the\n * existing {@link PostCard} (which already composes {@link ReactionBar} +\n * {@link CommentThread} when expanded) so the thread view stays in lock-step\n * with the inline one instead of duplicating rendering logic.\n */\n\nimport { ArrowLeft } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router-dom';\nimport { PageLayout } from '../../components/PageLayout';\nimport { PostCard } from '../../components/PostCard';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComPost, useComCommentAddedLive } from '../../hooks';\nimport { useComradeCircle } from '../../context';\n\nexport function PostThreadPage() {\n const { postId } = useParams<{ postId: string }>();\n const { navigateTo } = useComradeCircle();\n const { data: post, isLoading, isError, refetch } = useComPost(postId);\n // BOFF-4455: live-update this thread's comments without a manual refresh.\n useComCommentAddedLive(postId);\n\n // Home feed is the one destination every thread is always reachable from\n // (channel feeds link here too), matching the fixed-destination back-button\n // convention already used by EventDetailPage / MemberProfilePage rather\n // than relying on browser history depth, which isn't reliable once this\n // MFE is mounted inside the host shell's own router.\n const backButton = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Thread\" actions={backButton}>\n <LoadingView label=\"Loading post…\" />\n </PageLayout>\n );\n }\n\n if (isError || !post) {\n return (\n <PageLayout title=\"Thread\" actions={backButton}>\n <ErrorView\n title=\"Post not found\"\n message=\"This post may have been removed, or you may not have access to it.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout title=\"Thread\" actions={backButton}>\n <div className=\"mx-auto max-w-2xl\">\n <PostCard post={post} defaultExpanded hideThreadLink />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,SAAgB,IAAiB;CAC/B,IAAM,EAAE,cAAW,EAA8B,GAC3C,EAAE,kBAAe,EAAiB,GAClC,EAAE,MAAM,GAAM,cAAW,YAAS,eAAY,EAAW,CAAM;CAErE,EAAuB,CAAM;CAO7B,IAAM,IACJ,kBAAC,GAAD;EAAQ,SAAQ;EAAQ,MAAK;EAAK,eAAe,EAAW,EAAE;YAA9D,CACE,kBAAC,GAAD,EAAW,WAAU,iBAAkB,CAAA,GAAC,OAClC;;CAuBV,OApBI,IAEA,kBAAC,GAAD;EAAY,OAAM;EAAS,SAAS;YAClC,kBAAC,GAAD,EAAa,OAAM,gBAAiB,CAAA;CAC1B,CAAA,IAIZ,KAAW,CAAC,IAEZ,kBAAC,GAAD;EAAY,OAAM;EAAS,SAAS;YAClC,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAQ;EACxB,CAAA;CACS,CAAA,IAKd,kBAAC,GAAD;EAAY,OAAM;EAAS,SAAS;YAClC,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IAAgB;IAAM,iBAAA;IAAgB,gBAAA;GAAgB,CAAA;EACnD,CAAA;CACK,CAAA;AAEhB"}
|