@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.
@@ -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 r } from "../../hooks/useComradeCircleApi.js";
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 i } from "../../components/MemberIdentity.js";
8
- import { describeError as a, notify as o } from "../../lib/toast.js";
9
- import { formatDateTime as s, formatEnumLabel as c, formatNumber as l } from "../../utils/formatters.js";
10
- import { EmptyState as u } from "../../components/EmptyState.js";
11
- import { ErrorView as d, LoadingView as f } from "../../components/StateViews.js";
12
- import { StatusBadge as p } from "../../components/StatusBadge.js";
13
- import { MiniBar as m } from "../../components/MiniBar.js";
14
- import { ArrowLeft as h, Ban as ne, CalendarDays as re, Check as g, CheckCircle2 as _, MapPin as v, Radio as y, Tag as b, Users as x, Video as S } from "lucide-react";
15
- import { useParams as C } from "react-router-dom";
16
- import { Fragment as ie, jsx as w, jsxs as T } from "react/jsx-runtime";
17
- import { Button as E, GlassCard as ae } from "@burdenoff/fe-libs/ui";
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 oe = [
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
- ], D = {
36
+ ], O = {
37
37
  GOING: 0,
38
38
  INTERESTED: 1,
39
39
  WAITLISTED: 2,
40
40
  NOT_GOING: 3
41
- }, O = [
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 k() {
60
- let { id: k } = C(), { currentUser: A, navigateTo: j } = e(), { data: M, isLoading: N, isError: P, refetch: F } = ee(k), I = te(), L = r(), R = /* @__PURE__ */ T(E, {
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: () => j("events"),
64
- children: [/* @__PURE__ */ w(h, { className: "mr-1.5 h-4 w-4" }), " Back"]
65
+ onClick: () => M("events"),
66
+ children: [/* @__PURE__ */ w(ne, { className: "mr-1.5 h-4 w-4" }), " Back"]
65
67
  });
66
- if (N) return /* @__PURE__ */ w(n, {
68
+ if (P) return /* @__PURE__ */ w(n, {
67
69
  title: "Event",
68
- actions: R,
69
- children: /* @__PURE__ */ w(f, { label: "Loading event…" })
70
+ actions: z,
71
+ children: /* @__PURE__ */ w(p, { label: "Loading event…" })
70
72
  });
71
- if (P || !M) return /* @__PURE__ */ w(n, {
73
+ if (F || !N) return /* @__PURE__ */ w(n, {
72
74
  title: "Event",
73
- actions: R,
74
- children: /* @__PURE__ */ w(d, {
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: () => F()
79
+ onRetry: () => I()
78
80
  })
79
81
  });
80
- let z = !!(A?.id && A.id === M.hostId), B = M.status === "LIVE", V = M.status === "SCHEDULED" || M.status === "LIVE", H = M.mode === "ONLINE" || M.mode === "HYBRID", U = M.mode === "IN_PERSON" || M.mode === "HYBRID", W = B && H && !!M.joinUrl, G = A?.id ? M.rsvps.find((e) => e.memberId === A.id) : void 0, K = M.capacity ?? null, q = K && K > 0 ? Math.min(100, M.rsvpCount / K * 100) : null, J = q != null && q >= 80, Y = q != null && q >= 100, X = q == null ? "" : Y ? "Full" : `${Math.round(q)}% full`, se = Y ? "bg-status-error-text" : J ? "bg-status-warning-text" : "bg-accent", ce = Y ? "text-status-error-text" : J ? "text-status-warning-text" : "text-text-tertiary", Z = M.rsvps.reduce((e, t) => (e[t.status] += 1, e), {
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
- }), le = [...M.rsvps].sort((e, t) => D[e.status] - D[t.status]), Q = O.filter((e) => Z[e.status] > 0).map((e) => `${l(Z[e.status])} ${e.label}`).join(" · "), ue = async (e) => {
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 I.mutateAsync({
88
- eventId: M.id,
89
+ await L.mutateAsync({
90
+ eventId: N.id,
89
91
  status: e
90
- }), o.success("RSVP updated", `${c(e)} · ${M.title}`);
92
+ }), s.success("RSVP updated", `${l(e)} · ${N.title}`);
91
93
  } catch (e) {
92
- o.error("Could not RSVP", a(e));
94
+ s.error("Could not RSVP", o(e));
93
95
  }
94
96
  }, $ = async (e, t) => {
95
97
  try {
96
- await L.mutateAsync({
97
- id: M.id,
98
+ await R.mutateAsync({
99
+ id: N.id,
98
100
  status: e
99
- }), o.success(t, M.title);
101
+ }), s.success(t, N.title);
100
102
  } catch (e) {
101
- o.error("Could not update the event", a(e));
103
+ s.error("Could not update the event", o(e));
102
104
  }
103
- }, de = M.endsAt ? `${s(M.startsAt)} – ${s(M.endsAt)}` : s(M.startsAt);
105
+ }, fe = N.endsAt ? `${c(N.startsAt)} – ${c(N.endsAt)}` : c(N.startsAt);
104
106
  return /* @__PURE__ */ w(n, {
105
- title: M.title,
106
- actions: R,
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(ae, {
112
+ /* @__PURE__ */ T(oe, {
111
113
  treatment: "plain",
112
- className: t("flex flex-col gap-5 p-5 sm:p-6", B && "ring-1 ring-[var(--color-accent-soft)]"),
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
- B && /* @__PURE__ */ w("span", {
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(p, { status: M.status }), /* @__PURE__ */ w(p, { status: M.mode })]
122
+ children: [/* @__PURE__ */ w(m, { status: N.status }), /* @__PURE__ */ w(m, { status: N.mode })]
121
123
  }),
122
- W && M.joinUrl && /* @__PURE__ */ w(E, {
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: M.joinUrl,
129
+ href: N.joinUrl,
128
130
  target: "_blank",
129
131
  rel: "noreferrer noopener",
130
- children: [/* @__PURE__ */ w(S, { className: "h-4 w-4" }), " Join the live event"]
132
+ children: [/* @__PURE__ */ w(x, { className: "h-4 w-4" }), " Join the live event"]
131
133
  })
132
134
  }),
133
- M.description && /* @__PURE__ */ w("p", {
135
+ N.description && /* @__PURE__ */ w("p", {
134
136
  className: "whitespace-pre-wrap text-sm text-text-secondary",
135
- children: M.description
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(re, { className: "mt-0.5 h-4 w-4 shrink-0 text-text-tertiary" }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("dt", {
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: de
149
+ children: fe
148
150
  })] })]
149
151
  }),
150
- U && M.location && /* @__PURE__ */ T("div", {
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: M.location
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(S, { className: "mt-0.5 h-4 w-4 shrink-0 text-text-tertiary" }), /* @__PURE__ */ T("div", {
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: B ? "A join link has not been provided." : "The join link becomes available when the event goes live."
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(x, { className: "mt-0.5 h-4 w-4 shrink-0 text-text-tertiary" }), /* @__PURE__ */ T("div", {
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: [l(M.rsvpCount), K ? ` / ${l(K)}` : ""] }), X && /* @__PURE__ */ w("span", {
191
- className: t("text-xs font-semibold", ce),
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(m, {
197
+ q != null && /* @__PURE__ */ w(h, {
196
198
  value: q,
197
- barClassName: se,
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
- M.tags.length > 0 && /* @__PURE__ */ T("div", {
207
+ N.tags.length > 0 && /* @__PURE__ */ T("div", {
206
208
  className: "flex flex-wrap items-center gap-1.5",
207
- children: [/* @__PURE__ */ w(b, {
209
+ children: [/* @__PURE__ */ w(ae, {
208
210
  className: "h-3.5 w-3.5 text-text-tertiary",
209
211
  "aria-hidden": "true"
210
- }), M.tags.map((e) => /* @__PURE__ */ T("span", {
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(i, {
221
- memberId: M.hostId,
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
- V && /* @__PURE__ */ T("section", {
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: oe.map((e) => {
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: () => ue(e.status),
247
- disabled: I.isPending,
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
- z && (M.status === "SCHEDULED" || M.status === "LIVE") && /* @__PURE__ */ T("section", {
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: [M.status === "SCHEDULED" && /* @__PURE__ */ T(ie, { children: [/* @__PURE__ */ T(E, {
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: L.isPending,
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: L.isPending,
279
- children: [/* @__PURE__ */ w(ne, { className: "mr-1.5 h-4 w-4" }), " Cancel"]
280
- })] }), M.status === "LIVE" && /* @__PURE__ */ T(E, {
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: L.isPending,
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(x, { className: "h-3.5 w-3.5" }),
307
+ /* @__PURE__ */ w(b, { className: "h-3.5 w-3.5" }),
306
308
  " ",
307
- l(M.rsvps.length)
309
+ u(N.rsvps.length)
308
310
  ]
309
311
  })]
310
- }), M.rsvps.length === 0 ? /* @__PURE__ */ w(u, {
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: le.map((e) => /* @__PURE__ */ T("li", {
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(i, {
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(p, { status: e.status })]
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 { k as EventDetailPage };
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&apos;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&apos;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,4CA2C7B"}
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 { useComPost as n } from "../../hooks/useComradeCircleApi.js";
4
+ import { useComCommentAddedLive as n, useComPost as r } from "../../hooks/useComradeCircleApi.js";
5
5
  import "../../hooks/index.js";
6
- import { PostCard as r } from "../../components/PostCard.js";
7
- import { ErrorView as i, LoadingView as a } from "../../components/StateViews.js";
8
- import { ArrowLeft as o } from "lucide-react";
9
- import { useParams as s } from "react-router-dom";
10
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
11
- import { Button as u } from "@burdenoff/fe-libs/ui";
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 d() {
14
- let { postId: d } = s(), { navigateTo: f } = e(), { data: p, isLoading: m, isError: h, refetch: g } = n(d), _ = /* @__PURE__ */ l(u, {
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: () => f(""),
18
- children: [/* @__PURE__ */ c(o, { className: "mr-1.5 h-4 w-4" }), " Back"]
19
+ onClick: () => p(""),
20
+ children: [/* @__PURE__ */ l(s, { className: "mr-1.5 h-4 w-4" }), " Back"]
19
21
  });
20
- return m ? /* @__PURE__ */ c(t, {
22
+ return h ? /* @__PURE__ */ l(t, {
21
23
  title: "Thread",
22
- actions: _,
23
- children: /* @__PURE__ */ c(a, { label: "Loading post…" })
24
- }) : h || !p ? /* @__PURE__ */ c(t, {
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__ */ c(i, {
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: () => g()
32
+ onRetry: () => _()
31
33
  })
32
- }) : /* @__PURE__ */ c(t, {
34
+ }) : /* @__PURE__ */ l(t, {
33
35
  title: "Thread",
34
- actions: _,
35
- children: /* @__PURE__ */ c("div", {
36
+ actions: v,
37
+ children: /* @__PURE__ */ l("div", {
36
38
  className: "mx-auto max-w-2xl",
37
- children: /* @__PURE__ */ c(r, {
38
- post: p,
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 { d as PostThreadPage };
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,GAO/D,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"}
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"}