@burdenoff/microfe-comradecircle 2026.819.1 → 2026.830.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.
Files changed (89) hide show
  1. package/dist/comradecircle/components/AvatarUploadField.js +8 -8
  2. package/dist/comradecircle/components/CircleCard.js +4 -4
  3. package/dist/comradecircle/components/CommentThread.d.ts.map +1 -1
  4. package/dist/comradecircle/components/CommentThread.js +66 -61
  5. package/dist/comradecircle/components/CommentThread.js.map +1 -1
  6. package/dist/comradecircle/components/CreateCircleDialog.d.ts.map +1 -1
  7. package/dist/comradecircle/components/CreateCircleDialog.js +89 -88
  8. package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -1
  9. package/dist/comradecircle/components/CreateEventDialog.d.ts.map +1 -1
  10. package/dist/comradecircle/components/CreateEventDialog.js +28 -27
  11. package/dist/comradecircle/components/CreateEventDialog.js.map +1 -1
  12. package/dist/comradecircle/components/DraftsPanel.d.ts +32 -0
  13. package/dist/comradecircle/components/DraftsPanel.d.ts.map +1 -0
  14. package/dist/comradecircle/components/DraftsPanel.js +176 -0
  15. package/dist/comradecircle/components/DraftsPanel.js.map +1 -0
  16. package/dist/comradecircle/components/EventCard.js +5 -5
  17. package/dist/comradecircle/components/FileImage.js +5 -5
  18. package/dist/comradecircle/components/ImageAttachments.js +21 -21
  19. package/dist/comradecircle/components/MemberIdentity.js +4 -4
  20. package/dist/comradecircle/components/MobileBottomNav.d.ts.map +1 -1
  21. package/dist/comradecircle/components/MobileBottomNav.js +37 -30
  22. package/dist/comradecircle/components/MobileBottomNav.js.map +1 -1
  23. package/dist/comradecircle/components/Pagination.d.ts.map +1 -1
  24. package/dist/comradecircle/components/Pagination.js +37 -28
  25. package/dist/comradecircle/components/Pagination.js.map +1 -1
  26. package/dist/comradecircle/components/PollCard.js +13 -13
  27. package/dist/comradecircle/components/PostCard.d.ts.map +1 -1
  28. package/dist/comradecircle/components/PostCard.js +102 -89
  29. package/dist/comradecircle/components/PostCard.js.map +1 -1
  30. package/dist/comradecircle/components/PostComposer.d.ts +11 -5
  31. package/dist/comradecircle/components/PostComposer.d.ts.map +1 -1
  32. package/dist/comradecircle/components/PostComposer.js +60 -48
  33. package/dist/comradecircle/components/PostComposer.js.map +1 -1
  34. package/dist/comradecircle/components/ReactionBar.d.ts.map +1 -1
  35. package/dist/comradecircle/components/ReactionBar.js +65 -55
  36. package/dist/comradecircle/components/ReactionBar.js.map +1 -1
  37. package/dist/comradecircle/components/StateViews.d.ts.map +1 -1
  38. package/dist/comradecircle/components/StateViews.js +38 -30
  39. package/dist/comradecircle/components/StateViews.js.map +1 -1
  40. package/dist/comradecircle/components/UpcomingEvents.d.ts +26 -0
  41. package/dist/comradecircle/components/UpcomingEvents.d.ts.map +1 -0
  42. package/dist/comradecircle/components/UpcomingEvents.js +126 -0
  43. package/dist/comradecircle/components/UpcomingEvents.js.map +1 -0
  44. package/dist/comradecircle/components/index.d.ts +2 -0
  45. package/dist/comradecircle/components/index.d.ts.map +1 -1
  46. package/dist/comradecircle/hooks/index.d.ts +1 -0
  47. package/dist/comradecircle/hooks/index.d.ts.map +1 -1
  48. package/dist/comradecircle/hooks/index.js +1 -0
  49. package/dist/comradecircle/hooks/useTr.d.ts +18 -0
  50. package/dist/comradecircle/hooks/useTr.d.ts.map +1 -0
  51. package/dist/comradecircle/hooks/useTr.js +12 -0
  52. package/dist/comradecircle/hooks/useTr.js.map +1 -0
  53. package/dist/comradecircle/pages/FeedPage.d.ts +5 -0
  54. package/dist/comradecircle/pages/FeedPage.d.ts.map +1 -1
  55. package/dist/comradecircle/pages/FeedPage.js +76 -62
  56. package/dist/comradecircle/pages/FeedPage.js.map +1 -1
  57. package/dist/comradecircle/pages/circles/CircleDetailPage.d.ts.map +1 -1
  58. package/dist/comradecircle/pages/circles/CircleDetailPage.js +454 -409
  59. package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
  60. package/dist/comradecircle/pages/circles/CirclesPage.d.ts.map +1 -1
  61. package/dist/comradecircle/pages/circles/CirclesPage.js +124 -117
  62. package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
  63. package/dist/comradecircle/pages/events/EventDetailPage.d.ts.map +1 -1
  64. package/dist/comradecircle/pages/events/EventDetailPage.js +182 -153
  65. package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
  66. package/dist/comradecircle/pages/events/EventsPage.d.ts.map +1 -1
  67. package/dist/comradecircle/pages/events/EventsPage.js +100 -91
  68. package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
  69. package/dist/comradecircle/pages/members/MemberProfilePage.d.ts.map +1 -1
  70. package/dist/comradecircle/pages/members/MemberProfilePage.js +172 -159
  71. package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
  72. package/dist/comradecircle/pages/members/MembersPage.d.ts.map +1 -1
  73. package/dist/comradecircle/pages/members/MembersPage.js +103 -104
  74. package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
  75. package/dist/comradecircle/pages/monetization/MonetizationPage.d.ts.map +1 -1
  76. package/dist/comradecircle/pages/monetization/MonetizationPage.js +319 -297
  77. package/dist/comradecircle/pages/monetization/MonetizationPage.js.map +1 -1
  78. package/dist/comradecircle/pages/posts/PostThreadPage.d.ts.map +1 -1
  79. package/dist/comradecircle/pages/posts/PostThreadPage.js +37 -32
  80. package/dist/comradecircle/pages/posts/PostThreadPage.js.map +1 -1
  81. package/dist/comradecircle/pages/profile/MyProfilePage.d.ts.map +1 -1
  82. package/dist/comradecircle/pages/profile/MyProfilePage.js +182 -177
  83. package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
  84. package/dist/comradecircle/utils/i18n.js +15 -0
  85. package/dist/comradecircle/utils/i18n.js.map +1 -0
  86. package/dist/comradecircle/utils/pageContext.d.ts.map +1 -1
  87. package/dist/comradecircle/utils/pageContext.js +3 -1
  88. package/dist/comradecircle/utils/pageContext.js.map +1 -1
  89. package/package.json +1 -1
@@ -1,111 +1,121 @@
1
1
  import { useComradeCircle as e } from "../context/ComradeCircleContext.js";
2
2
  import "../context/index.js";
3
- import { cn as t } from "../utils/cn.js";
4
- import { useComReactToPost as n } from "../hooks/useComradeCircleApi.js";
3
+ import { useComReactToPost as t } from "../hooks/useComradeCircleApi.js";
4
+ import { useTr as n } from "../hooks/useTr.js";
5
5
  import "../hooks/index.js";
6
- import { describeError as r, notify as i } from "../lib/toast.js";
7
- import { useState as a } from "react";
8
- import { SmilePlus as o } from "lucide-react";
9
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
10
- import { Popover as l, PopoverContent as u, PopoverTrigger as d } from "@burdenoff/fe-libs/ui";
6
+ import { cn as r } from "../utils/cn.js";
7
+ import { describeError as i, notify as a } from "../lib/toast.js";
8
+ import { useState as o } from "react";
9
+ import { SmilePlus as s } from "lucide-react";
10
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
11
+ import { Popover as u, PopoverContent as d, PopoverTrigger as f } from "@burdenoff/fe-libs/ui";
11
12
  //#region src/comradecircle/components/ReactionBar.tsx
12
- var f = [
13
+ var p = [
13
14
  {
14
15
  type: "LIKE",
15
16
  emoji: "👍",
16
- label: "Like"
17
+ key: "comradecircle.components.reactionBar.like",
18
+ fallback: "Like"
17
19
  },
18
20
  {
19
21
  type: "LOVE",
20
22
  emoji: "❤️",
21
- label: "Love"
23
+ key: "comradecircle.components.reactionBar.love",
24
+ fallback: "Love"
22
25
  },
23
26
  {
24
27
  type: "CELEBRATE",
25
28
  emoji: "🎉",
26
- label: "Celebrate"
29
+ key: "comradecircle.components.reactionBar.celebrate",
30
+ fallback: "Celebrate"
27
31
  },
28
32
  {
29
33
  type: "INSIGHTFUL",
30
34
  emoji: "💡",
31
- label: "Insightful"
35
+ key: "comradecircle.components.reactionBar.insightful",
36
+ fallback: "Insightful"
32
37
  },
33
38
  {
34
39
  type: "LAUGH",
35
40
  emoji: "😄",
36
- label: "Funny"
41
+ key: "comradecircle.components.reactionBar.funny",
42
+ fallback: "Funny"
37
43
  },
38
44
  {
39
45
  type: "SUPPORT",
40
46
  emoji: "🤝",
41
- label: "Support"
47
+ key: "comradecircle.components.reactionBar.support",
48
+ fallback: "Support"
42
49
  }
43
50
  ];
44
- function p({ post: p }) {
45
- let { currentUser: m } = e(), h = n(), [g, _] = a(!1), v = m?.id, y = /* @__PURE__ */ new Map();
46
- for (let e of p.reactions ?? []) {
47
- let t = y.get(e.type) ?? {
51
+ function m({ post: m }) {
52
+ let { currentUser: h } = e(), g = t(), _ = n(), [v, y] = o(!1), b = h?.id, x = /* @__PURE__ */ new Map();
53
+ for (let e of m.reactions ?? []) {
54
+ let t = x.get(e.type) ?? {
48
55
  count: 0,
49
56
  mine: !1
50
57
  };
51
- t.count += 1, v && e.memberId === v && (t.mine = !0), y.set(e.type, t);
58
+ t.count += 1, b && e.memberId === b && (t.mine = !0), x.set(e.type, t);
52
59
  }
53
- let b = async (e) => {
54
- _(!1);
60
+ let S = async (e) => {
61
+ y(!1);
55
62
  try {
56
- await h.mutateAsync({
57
- postId: p.id,
63
+ await g.mutateAsync({
64
+ postId: m.id,
58
65
  type: e
59
66
  });
60
67
  } catch (e) {
61
- i.error("Could not react", r(e));
68
+ a.error(_("comradecircle.components.reactionBar.reactError", "Could not react"), i(e));
62
69
  }
63
70
  };
64
- return /* @__PURE__ */ c("div", {
71
+ return /* @__PURE__ */ l("div", {
65
72
  className: "flex flex-wrap items-center gap-1.5",
66
- children: [f.filter((e) => (y.get(e.type)?.count ?? 0) > 0).map((e) => {
67
- let n = y.get(e.type);
68
- return /* @__PURE__ */ c("button", {
73
+ children: [p.filter((e) => (x.get(e.type)?.count ?? 0) > 0).map((e) => {
74
+ let t = x.get(e.type);
75
+ return /* @__PURE__ */ l("button", {
69
76
  type: "button",
70
- onClick: () => b(e.type),
71
- disabled: h.isPending,
72
- "aria-pressed": n.mine,
73
- "aria-label": `${e.label} (${n.count})`,
74
- className: t("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-transform duration-150 active:scale-90", n.mine ? "border-accent bg-[var(--color-accent-soft)] text-text-primary shadow-[0_0_0_2px_var(--color-accent-soft)]" : "border-border-seam bg-bg-surface text-text-secondary hover:-translate-y-px hover:bg-bg-sunken"),
75
- children: [/* @__PURE__ */ s("span", {
77
+ onClick: () => S(e.type),
78
+ disabled: g.isPending,
79
+ "aria-pressed": t.mine,
80
+ "aria-label": _("comradecircle.components.reactionBar.reactionAriaLabel", "{{label}} ({{count}})", {
81
+ label: _(e.key, e.fallback),
82
+ count: t.count
83
+ }),
84
+ className: r("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-transform duration-150 active:scale-90", t.mine ? "border-accent bg-[var(--color-accent-soft)] text-text-primary shadow-[0_0_0_2px_var(--color-accent-soft)]" : "border-border-seam bg-bg-surface text-text-secondary hover:-translate-y-px hover:bg-bg-sunken"),
85
+ children: [/* @__PURE__ */ c("span", {
76
86
  "aria-hidden": "true",
77
87
  children: e.emoji
78
- }), /* @__PURE__ */ s("span", {
88
+ }), /* @__PURE__ */ c("span", {
79
89
  className: "tabular-nums",
80
- children: n.count
90
+ children: t.count
81
91
  })]
82
92
  }, e.type);
83
- }), /* @__PURE__ */ c(l, {
84
- open: g,
85
- onOpenChange: _,
86
- children: [/* @__PURE__ */ s(d, {
93
+ }), /* @__PURE__ */ l(u, {
94
+ open: v,
95
+ onOpenChange: y,
96
+ children: [/* @__PURE__ */ c(f, {
87
97
  asChild: !0,
88
- children: /* @__PURE__ */ s("button", {
98
+ children: /* @__PURE__ */ c("button", {
89
99
  type: "button",
90
- "aria-label": "Add reaction",
100
+ "aria-label": _("comradecircle.components.reactionBar.addReactionAriaLabel", "Add reaction"),
91
101
  className: "inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-surface px-2 py-0.5 text-xs text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary",
92
- children: /* @__PURE__ */ s(o, { className: "h-3.5 w-3.5" })
102
+ children: /* @__PURE__ */ c(s, { className: "h-3.5 w-3.5" })
93
103
  })
94
- }), /* @__PURE__ */ s(u, {
104
+ }), /* @__PURE__ */ c(d, {
95
105
  className: "w-auto p-1.5",
96
106
  align: "start",
97
- children: /* @__PURE__ */ s("div", {
107
+ children: /* @__PURE__ */ c("div", {
98
108
  className: "flex items-center gap-0.5",
99
- children: f.map((e) => {
100
- let n = y.get(e.type)?.mine ?? !1;
101
- return /* @__PURE__ */ s("button", {
109
+ children: p.map((e) => {
110
+ let t = x.get(e.type)?.mine ?? !1, n = _(e.key, e.fallback);
111
+ return /* @__PURE__ */ c("button", {
102
112
  type: "button",
103
- onClick: () => b(e.type),
104
- disabled: h.isPending,
105
- title: e.label,
106
- "aria-label": e.label,
107
- className: t("flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken", n && "bg-[var(--color-accent-soft)]"),
108
- children: /* @__PURE__ */ s("span", {
113
+ onClick: () => S(e.type),
114
+ disabled: g.isPending,
115
+ title: n,
116
+ "aria-label": n,
117
+ className: r("flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken", t && "bg-[var(--color-accent-soft)]"),
118
+ children: /* @__PURE__ */ c("span", {
109
119
  "aria-hidden": "true",
110
120
  children: e.emoji
111
121
  })
@@ -117,6 +127,6 @@ function p({ post: p }) {
117
127
  });
118
128
  }
119
129
  //#endregion
120
- export { p as ReactionBar };
130
+ export { m as ReactionBar };
121
131
 
122
132
  //# sourceMappingURL=ReactionBar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactionBar.js","names":[],"sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"sourcesContent":["/**\n * ReactionBar — Slack-style multi-reaction control for a post.\n * @module comradecircle/components\n *\n * `comReactToPost(postId, type)` is an idempotent toggle for one typed reaction,\n * so a member may hold several distinct reactions on a post. We render a chip\n * per reaction type that has a count (highlighted when the caller reacted) plus\n * a popover to add any of the six types.\n */\n\nimport { useState } from 'react';\nimport { SmilePlus } from 'lucide-react';\nimport { Popover, PopoverContent, PopoverTrigger } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComReactToPost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost, ComReactionType } from '../types';\n\nconst REACTIONS: { type: ComReactionType; emoji: string; label: string }[] = [\n { type: 'LIKE', emoji: '👍', label: 'Like' },\n { type: 'LOVE', emoji: '❤️', label: 'Love' },\n { type: 'CELEBRATE', emoji: '🎉', label: 'Celebrate' },\n { type: 'INSIGHTFUL', emoji: '💡', label: 'Insightful' },\n { type: 'LAUGH', emoji: '😄', label: 'Funny' },\n { type: 'SUPPORT', emoji: '🤝', label: 'Support' },\n];\n\ninterface ReactionBarProps {\n post: ComPost;\n}\n\nexport function ReactionBar({ post }: ReactionBarProps) {\n const { currentUser } = useComradeCircle();\n const react = useComReactToPost();\n const [open, setOpen] = useState(false);\n\n const meId = currentUser?.id;\n const counts = new Map<ComReactionType, { count: number; mine: boolean }>();\n for (const r of post.reactions ?? []) {\n const cur = counts.get(r.type) ?? { count: 0, mine: false };\n cur.count += 1;\n if (meId && r.memberId === meId) cur.mine = true;\n counts.set(r.type, cur);\n }\n\n const toggle = async (type: ComReactionType) => {\n setOpen(false);\n try {\n await react.mutateAsync({ postId: post.id, type });\n } catch (err) {\n notify.error('Could not react', describeError(err));\n }\n };\n\n const active = REACTIONS.filter((r) => (counts.get(r.type)?.count ?? 0) > 0);\n\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {active.map((r) => {\n const info = counts.get(r.type)!;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n aria-pressed={info.mine}\n aria-label={`${r.label} (${info.count})`}\n className={cn(\n 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-transform duration-150 active:scale-90',\n info.mine\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary shadow-[0_0_0_2px_var(--color-accent-soft)]'\n : 'border-border-seam bg-bg-surface text-text-secondary hover:-translate-y-px hover:bg-bg-sunken'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n <span className=\"tabular-nums\">{info.count}</span>\n </button>\n );\n })}\n\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Add reaction\"\n className=\"inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-surface px-2 py-0.5 text-xs text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <SmilePlus className=\"h-3.5 w-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-1.5\" align=\"start\">\n <div className=\"flex items-center gap-0.5\">\n {REACTIONS.map((r) => {\n const mine = counts.get(r.type)?.mine ?? false;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n title={r.label}\n aria-label={r.label}\n className={cn(\n 'flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken',\n mine && 'bg-[var(--color-accent-soft)]'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n </button>\n );\n })}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,IAAuE;CAC3E;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;CAAO;CAC3C;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;CAAO;CAC3C;EAAE,MAAM;EAAa,OAAO;EAAM,OAAO;CAAY;CACrD;EAAE,MAAM;EAAc,OAAO;EAAM,OAAO;CAAa;CACvD;EAAE,MAAM;EAAS,OAAO;EAAM,OAAO;CAAQ;CAC7C;EAAE,MAAM;EAAW,OAAO;EAAM,OAAO;CAAU;AACnD;AAMA,SAAgB,EAAY,EAAE,WAA0B;CACtD,IAAM,EAAE,mBAAgB,EAAiB,GACnC,IAAQ,EAAkB,GAC1B,CAAC,GAAM,KAAW,EAAS,EAAK,GAEhC,IAAO,GAAa,IACpB,oBAAS,IAAI,IAAuD;CAC1E,KAAK,IAAM,KAAK,EAAK,aAAa,CAAC,GAAG;EACpC,IAAM,IAAM,EAAO,IAAI,EAAE,IAAI,KAAK;GAAE,OAAO;GAAG,MAAM;EAAM;EAG1D,AAFA,EAAI,SAAS,GACT,KAAQ,EAAE,aAAa,MAAM,EAAI,OAAO,KAC5C,EAAO,IAAI,EAAE,MAAM,CAAG;CACxB;CAEA,IAAM,IAAS,OAAO,MAA0B;EAC9C,EAAQ,EAAK;EACb,IAAI;GACF,MAAM,EAAM,YAAY;IAAE,QAAQ,EAAK;IAAI;GAAK,CAAC;EACnD,SAAS,GAAK;GACZ,EAAO,MAAM,mBAAmB,EAAc,CAAG,CAAC;EACpD;CACF;CAIA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAHa,EAAU,QAAQ,OAAO,EAAO,IAAI,EAAE,IAAI,GAAG,SAAS,KAAK,CAIrE,EAAO,KAAK,MAAM;GACjB,IAAM,IAAO,EAAO,IAAI,EAAE,IAAI;GAC9B,OACE,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAO,EAAE,IAAI;IAC5B,UAAU,EAAM;IAChB,gBAAc,EAAK;IACnB,cAAY,GAAG,EAAE,MAAM,IAAI,EAAK,MAAM;IACtC,WAAW,EACT,wIACA,EAAK,OACD,8GACA,+FACN;cAZF,CAcE,kBAAC,QAAD;KAAM,eAAY;eAAQ,EAAE;IAAY,CAAA,GACxC,kBAAC,QAAD;KAAM,WAAU;eAAgB,EAAK;IAAY,CAAA,CAC3C;MAfD,EAAE,IAeD;EAEZ,CAAC,GAED,kBAAC,GAAD;GAAe;GAAM,cAAc;aAAnC,CACE,kBAAC,GAAD;IAAgB,SAAA;cACd,kBAAC,UAAD;KACE,MAAK;KACL,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,cAAe,CAAA;IAC9B,CAAA;GACM,CAAA,GAChB,kBAAC,GAAD;IAAgB,WAAU;IAAe,OAAM;cAC7C,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MAAM;MACpB,IAAM,IAAO,EAAO,IAAI,EAAE,IAAI,GAAG,QAAQ;MACzC,OACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAO,EAAE,IAAI;OAC5B,UAAU,EAAM;OAChB,OAAO,EAAE;OACT,cAAY,EAAE;OACd,WAAW,EACT,uHACA,KAAQ,+BACV;iBAEA,kBAAC,QAAD;QAAM,eAAY;kBAAQ,EAAE;OAAY,CAAA;MAClC,GAZD,EAAE,IAYD;KAEZ,CAAC;IACE,CAAA;GACS,CAAA,CACT;IACN;;AAET"}
1
+ {"version":3,"file":"ReactionBar.js","names":[],"sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"sourcesContent":["/**\n * ReactionBar — Slack-style multi-reaction control for a post.\n * @module comradecircle/components\n *\n * `comReactToPost(postId, type)` is an idempotent toggle for one typed reaction,\n * so a member may hold several distinct reactions on a post. We render a chip\n * per reaction type that has a count (highlighted when the caller reacted) plus\n * a popover to add any of the six types.\n */\n\nimport { useState } from 'react';\nimport { SmilePlus } from 'lucide-react';\nimport { Popover, PopoverContent, PopoverTrigger } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComReactToPost, useTr } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost, ComReactionType } from '../types';\n\nconst REACTIONS: { type: ComReactionType; emoji: string; key: string; fallback: string }[] = [\n { type: 'LIKE', emoji: '👍', key: 'comradecircle.components.reactionBar.like', fallback: 'Like' },\n { type: 'LOVE', emoji: '❤️', key: 'comradecircle.components.reactionBar.love', fallback: 'Love' },\n {\n type: 'CELEBRATE',\n emoji: '🎉',\n key: 'comradecircle.components.reactionBar.celebrate',\n fallback: 'Celebrate',\n },\n {\n type: 'INSIGHTFUL',\n emoji: '💡',\n key: 'comradecircle.components.reactionBar.insightful',\n fallback: 'Insightful',\n },\n {\n type: 'LAUGH',\n emoji: '😄',\n key: 'comradecircle.components.reactionBar.funny',\n fallback: 'Funny',\n },\n {\n type: 'SUPPORT',\n emoji: '🤝',\n key: 'comradecircle.components.reactionBar.support',\n fallback: 'Support',\n },\n];\n\ninterface ReactionBarProps {\n post: ComPost;\n}\n\nexport function ReactionBar({ post }: ReactionBarProps) {\n const { currentUser } = useComradeCircle();\n const react = useComReactToPost();\n const tr = useTr();\n const [open, setOpen] = useState(false);\n\n const meId = currentUser?.id;\n const counts = new Map<ComReactionType, { count: number; mine: boolean }>();\n for (const r of post.reactions ?? []) {\n const cur = counts.get(r.type) ?? { count: 0, mine: false };\n cur.count += 1;\n if (meId && r.memberId === meId) cur.mine = true;\n counts.set(r.type, cur);\n }\n\n const toggle = async (type: ComReactionType) => {\n setOpen(false);\n try {\n await react.mutateAsync({ postId: post.id, type });\n } catch (err) {\n notify.error(\n tr('comradecircle.components.reactionBar.reactError', 'Could not react'),\n describeError(err)\n );\n }\n };\n\n const active = REACTIONS.filter((r) => (counts.get(r.type)?.count ?? 0) > 0);\n\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {active.map((r) => {\n const info = counts.get(r.type)!;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n aria-pressed={info.mine}\n aria-label={tr(\n 'comradecircle.components.reactionBar.reactionAriaLabel',\n '{{label}} ({{count}})',\n {\n label: tr(r.key, r.fallback),\n count: info.count,\n }\n )}\n className={cn(\n 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-transform duration-150 active:scale-90',\n info.mine\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary shadow-[0_0_0_2px_var(--color-accent-soft)]'\n : 'border-border-seam bg-bg-surface text-text-secondary hover:-translate-y-px hover:bg-bg-sunken'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n <span className=\"tabular-nums\">{info.count}</span>\n </button>\n );\n })}\n\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={tr(\n 'comradecircle.components.reactionBar.addReactionAriaLabel',\n 'Add reaction'\n )}\n className=\"inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-surface px-2 py-0.5 text-xs text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <SmilePlus className=\"h-3.5 w-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-1.5\" align=\"start\">\n <div className=\"flex items-center gap-0.5\">\n {REACTIONS.map((r) => {\n const mine = counts.get(r.type)?.mine ?? false;\n const label = tr(r.key, r.fallback);\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n title={label}\n aria-label={label}\n className={cn(\n 'flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken',\n mine && 'bg-[var(--color-accent-soft)]'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n </button>\n );\n })}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,IAAM,IAAuF;CAC3F;EAAE,MAAM;EAAQ,OAAO;EAAM,KAAK;EAA6C,UAAU;CAAO;CAChG;EAAE,MAAM;EAAQ,OAAO;EAAM,KAAK;EAA6C,UAAU;CAAO;CAChG;EACE,MAAM;EACN,OAAO;EACP,KAAK;EACL,UAAU;CACZ;CACA;EACE,MAAM;EACN,OAAO;EACP,KAAK;EACL,UAAU;CACZ;CACA;EACE,MAAM;EACN,OAAO;EACP,KAAK;EACL,UAAU;CACZ;CACA;EACE,MAAM;EACN,OAAO;EACP,KAAK;EACL,UAAU;CACZ;AACF;AAMA,SAAgB,EAAY,EAAE,WAA0B;CACtD,IAAM,EAAE,mBAAgB,EAAiB,GACnC,IAAQ,EAAkB,GAC1B,IAAK,EAAM,GACX,CAAC,GAAM,KAAW,EAAS,EAAK,GAEhC,IAAO,GAAa,IACpB,oBAAS,IAAI,IAAuD;CAC1E,KAAK,IAAM,KAAK,EAAK,aAAa,CAAC,GAAG;EACpC,IAAM,IAAM,EAAO,IAAI,EAAE,IAAI,KAAK;GAAE,OAAO;GAAG,MAAM;EAAM;EAG1D,AAFA,EAAI,SAAS,GACT,KAAQ,EAAE,aAAa,MAAM,EAAI,OAAO,KAC5C,EAAO,IAAI,EAAE,MAAM,CAAG;CACxB;CAEA,IAAM,IAAS,OAAO,MAA0B;EAC9C,EAAQ,EAAK;EACb,IAAI;GACF,MAAM,EAAM,YAAY;IAAE,QAAQ,EAAK;IAAI;GAAK,CAAC;EACnD,SAAS,GAAK;GACZ,EAAO,MACL,EAAG,mDAAmD,iBAAiB,GACvE,EAAc,CAAG,CACnB;EACF;CACF;CAIA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAHa,EAAU,QAAQ,OAAO,EAAO,IAAI,EAAE,IAAI,GAAG,SAAS,KAAK,CAIrE,EAAO,KAAK,MAAM;GACjB,IAAM,IAAO,EAAO,IAAI,EAAE,IAAI;GAC9B,OACE,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAO,EAAE,IAAI;IAC5B,UAAU,EAAM;IAChB,gBAAc,EAAK;IACnB,cAAY,EACV,0DACA,yBACA;KACE,OAAO,EAAG,EAAE,KAAK,EAAE,QAAQ;KAC3B,OAAO,EAAK;IACd,CACF;IACA,WAAW,EACT,wIACA,EAAK,OACD,8GACA,+FACN;cAnBF,CAqBE,kBAAC,QAAD;KAAM,eAAY;eAAQ,EAAE;IAAY,CAAA,GACxC,kBAAC,QAAD;KAAM,WAAU;eAAgB,EAAK;IAAY,CAAA,CAC3C;MAtBD,EAAE,IAsBD;EAEZ,CAAC,GAED,kBAAC,GAAD;GAAe;GAAM,cAAc;aAAnC,CACE,kBAAC,GAAD;IAAgB,SAAA;cACd,kBAAC,UAAD;KACE,MAAK;KACL,cAAY,EACV,6DACA,cACF;KACA,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,cAAe,CAAA;IAC9B,CAAA;GACM,CAAA,GAChB,kBAAC,GAAD;IAAgB,WAAU;IAAe,OAAM;cAC7C,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MAAM;MACpB,IAAM,IAAO,EAAO,IAAI,EAAE,IAAI,GAAG,QAAQ,IACnC,IAAQ,EAAG,EAAE,KAAK,EAAE,QAAQ;MAClC,OACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAO,EAAE,IAAI;OAC5B,UAAU,EAAM;OAChB,OAAO;OACP,cAAY;OACZ,WAAW,EACT,uHACA,KAAQ,+BACV;iBAEA,kBAAC,QAAD;QAAM,eAAY;kBAAQ,EAAE;OAAY,CAAA;MAClC,GAZD,EAAE,IAYD;KAEZ,CAAC;IACE,CAAA;GACS,CAAA,CACT;IACN;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"StateViews.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/StateViews.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK3C,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAK5C,CAAC;AAEF,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CA2BxC,CAAC;AAEF,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CAWpC,CAAC"}
1
+ {"version":3,"file":"StateViews.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/StateViews.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAM3C,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAU5C,CAAC;AAEF,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CA2BxC,CAAC;AAEF,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CAWpC,CAAC"}
@@ -1,34 +1,42 @@
1
- import { cn as e } from "../utils/cn.js";
2
- import { AlertCircle as t } from "lucide-react";
3
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
- import { Spinner as i } from "@burdenoff/fe-libs/ui";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import "../hooks/index.js";
3
+ import { cn as t } from "../utils/cn.js";
4
+ import { AlertCircle as n } from "lucide-react";
5
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
6
+ import { Spinner as a } from "@burdenoff/fe-libs/ui";
5
7
  //#region src/comradecircle/components/StateViews.tsx
6
- var a = ({ label: t = "Loading…", className: a }) => /* @__PURE__ */ r("div", {
7
- className: e("flex min-h-[40vh] flex-col items-center justify-center gap-3", a),
8
- children: [/* @__PURE__ */ n(i, { size: "lg" }), /* @__PURE__ */ n("p", {
9
- className: "text-sm text-text-secondary",
10
- children: t
11
- })]
12
- }), o = ({ title: i = "Something went wrong", message: a, onRetry: o, className: s }) => /* @__PURE__ */ r("div", {
13
- className: e("flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center", s),
14
- children: [
15
- /* @__PURE__ */ n(t, { className: "h-10 w-10 text-status-error-text" }),
16
- /* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("h2", {
17
- className: "text-lg font-semibold text-text-primary",
18
- children: i
19
- }), a && /* @__PURE__ */ n("p", {
20
- className: "mt-1 max-w-md text-sm text-text-secondary",
21
- children: a
22
- })] }),
23
- o && /* @__PURE__ */ n("button", {
24
- type: "button",
25
- onClick: o,
26
- className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
27
- children: "Retry"
28
- })
29
- ]
30
- });
8
+ var o = ({ label: n, className: o }) => {
9
+ let s = e();
10
+ return /* @__PURE__ */ i("div", {
11
+ className: t("flex min-h-[40vh] flex-col items-center justify-center gap-3", o),
12
+ children: [/* @__PURE__ */ r(a, { size: "lg" }), /* @__PURE__ */ r("p", {
13
+ className: "text-sm text-text-secondary",
14
+ children: n ?? s("comradecircle.components.stateViews.loading", "Loading…")
15
+ })]
16
+ });
17
+ }, s = ({ title: a, message: o, onRetry: s, className: c }) => {
18
+ let l = e();
19
+ return /* @__PURE__ */ i("div", {
20
+ className: t("flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center", c),
21
+ children: [
22
+ /* @__PURE__ */ r(n, { className: "h-10 w-10 text-status-error-text" }),
23
+ /* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("h2", {
24
+ className: "text-lg font-semibold text-text-primary",
25
+ children: a ?? l("comradecircle.components.stateViews.errorTitle", "Something went wrong")
26
+ }), o && /* @__PURE__ */ r("p", {
27
+ className: "mt-1 max-w-md text-sm text-text-secondary",
28
+ children: o
29
+ })] }),
30
+ s && /* @__PURE__ */ r("button", {
31
+ type: "button",
32
+ onClick: s,
33
+ className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
34
+ children: l("comradecircle.components.stateViews.retry", "Retry")
35
+ })
36
+ ]
37
+ });
38
+ };
31
39
  //#endregion
32
- export { o as ErrorView, a as LoadingView };
40
+ export { s as ErrorView, o as LoadingView };
33
41
 
34
42
  //# sourceMappingURL=StateViews.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/comradecircle/components/StateViews.tsx"],"sourcesContent":["/**\n * Shared loading / error / section state views\n * @module comradecircle/components\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface LoadingViewProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingView: FC<LoadingViewProps> = ({ label = 'Loading…', className }) => (\n <div className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}>\n <Spinner size=\"lg\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorViewProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorView: FC<ErrorViewProps> = ({\n title = 'Something went wrong',\n message,\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n {message && <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{message}</p>}\n </div>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Retry\n </button>\n )}\n </div>\n);\n\ninterface SectionProps {\n title: string;\n description?: string;\n action?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const Section: FC<SectionProps> = ({ title, description, action, children, className }) => (\n <section className={cn('mt-8 first:mt-0', className)}>\n <div className=\"mb-3 flex items-end justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {description && <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>}\n </div>\n {action && <div className=\"shrink-0\">{action}</div>}\n </div>\n {children}\n </section>\n);\n"],"mappings":";;;;;AAeA,IAAa,KAAqC,EAAE,WAAQ,YAAY,mBACtE,kBAAC,OAAD;CAAK,WAAW,EAAG,gEAAgE,CAAS;WAA5F,CACE,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA,GACpB,kBAAC,KAAD;EAAG,WAAU;YAA+B;CAAS,CAAA,CAClD;IAUM,KAAiC,EAC5C,WAAQ,wBACR,YACA,YACA,mBAEA,kBAAC,OAAD;CACE,WAAW,EACT,4EACA,CACF;WAJF;EAME,kBAAC,GAAD,EAAa,WAAU,mCAAoC,CAAA;EAC3D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAA2C;EAAU,CAAA,GAClE,KAAW,kBAAC,KAAD;GAAG,WAAU;aAA6C;EAAW,CAAA,CAC9E,EAAA,CAAA;EACJ,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aACX;EAEO,CAAA;CAEP"}
1
+ {"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/comradecircle/components/StateViews.tsx"],"sourcesContent":["/**\n * Shared loading / error / section state views\n * @module comradecircle/components\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../hooks';\nimport { cn } from '../utils/cn';\n\ninterface LoadingViewProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingView: FC<LoadingViewProps> = ({ label, className }) => {\n const tr = useTr();\n return (\n <div className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}>\n <Spinner size=\"lg\" />\n <p className=\"text-sm text-text-secondary\">\n {label ?? tr('comradecircle.components.stateViews.loading', 'Loading…')}\n </p>\n </div>\n );\n};\n\ninterface ErrorViewProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorView: FC<ErrorViewProps> = ({ title, message, onRetry, className }) => {\n const tr = useTr();\n return (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {title ?? tr('comradecircle.components.stateViews.errorTitle', 'Something went wrong')}\n </h2>\n {message && <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{message}</p>}\n </div>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('comradecircle.components.stateViews.retry', 'Retry')}\n </button>\n )}\n </div>\n );\n};\n\ninterface SectionProps {\n title: string;\n description?: string;\n action?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const Section: FC<SectionProps> = ({ title, description, action, children, className }) => (\n <section className={cn('mt-8 first:mt-0', className)}>\n <div className=\"mb-3 flex items-end justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {description && <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>}\n </div>\n {action && <div className=\"shrink-0\">{action}</div>}\n </div>\n {children}\n </section>\n);\n"],"mappings":";;;;;;;AAgBA,IAAa,KAAqC,EAAE,UAAO,mBAAgB;CACzE,IAAM,IAAK,EAAM;CACjB,OACE,kBAAC,OAAD;EAAK,WAAW,EAAG,gEAAgE,CAAS;YAA5F,CACE,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA,GACpB,kBAAC,KAAD;GAAG,WAAU;aACV,KAAS,EAAG,+CAA+C,UAAU;EACrE,CAAA,CACA;;AAET,GASa,KAAiC,EAAE,UAAO,YAAS,YAAS,mBAAgB;CACvF,IAAM,IAAK,EAAM;CACjB,OACE,kBAAC,OAAD;EACE,WAAW,EACT,4EACA,CACF;YAJF;GAME,kBAAC,GAAD,EAAa,WAAU,mCAAoC,CAAA;GAC3D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,KAAS,EAAG,kDAAkD,sBAAsB;GACnF,CAAA,GACH,KAAW,kBAAC,KAAD;IAAG,WAAU;cAA6C;GAAW,CAAA,CAC9E,EAAA,CAAA;GACJ,KACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAET,EAAG,6CAA6C,OAAO;GAClD,CAAA;EAEP;;AAET"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * UpcomingEvents — a compact "what's next" strip for the home feed.
3
+ * @module comradecircle/components
4
+ *
5
+ * Reads `comListEvents(status: SCHEDULED)` — the same root field the Events page
6
+ * already uses — and writes `comRsvpEvent(eventId, status)` straight from the
7
+ * row, so the member can commit to the next gathering without leaving home.
8
+ * Every value shown (`title`, `startsAt`, `mode`, `location`, `rsvpCount`,
9
+ * `capacity`) comes from that query's own selection set.
10
+ *
11
+ * Why the client-side date filter: the svc orders `comListEvents` by
12
+ * `startsAt: 'asc'` and does NOT drop events whose start time has passed, so an
13
+ * event left in `SCHEDULED` after its slot sorts *ahead* of genuinely upcoming
14
+ * ones. The rail therefore reads a window of the ordered list and keeps only
15
+ * rows still in the future. If that window is entirely historical the strip
16
+ * renders nothing at all — it never claims "no upcoming events", because one
17
+ * page of a list is not evidence that none exist.
18
+ */
19
+ interface UpcomingEventsProps {
20
+ /** Rows to render once past events are filtered out. */
21
+ limit?: number;
22
+ className?: string;
23
+ }
24
+ export declare function UpcomingEvents({ limit, className }: UpcomingEventsProps): import("react/jsx-runtime").JSX.Element | null;
25
+ export {};
26
+ //# sourceMappingURL=UpcomingEvents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpcomingEvents.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/UpcomingEvents.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgCH,UAAU,mBAAmB;IAC3B,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAS,EAAE,SAAS,EAAE,EAAE,mBAAmB,kDAgD3E"}
@@ -0,0 +1,126 @@
1
+ import { useComradeCircle as e } from "../context/ComradeCircleContext.js";
2
+ import "../context/index.js";
3
+ import { useComEvents as t, useComRsvpEvent as n } from "../hooks/useComradeCircleApi.js";
4
+ import "../hooks/index.js";
5
+ import { cn as r } from "../utils/cn.js";
6
+ import { describeError as i, notify as a } from "../lib/toast.js";
7
+ import { formatDateTime as o, formatNumber as s } from "../utils/formatters.js";
8
+ import { useEffect as c, useMemo as l, useState as u } from "react";
9
+ import { ArrowRight as d, CalendarDays as f, Check as p, MapPin as m, Users as h, Video as g } from "lucide-react";
10
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
11
+ import { Button as b } from "@burdenoff/fe-libs/ui";
12
+ //#region src/comradecircle/components/UpcomingEvents.tsx
13
+ var x = 50, S = 6e4;
14
+ function C(e = S) {
15
+ let [t, n] = u(() => Date.now());
16
+ return c(() => {
17
+ let t = setInterval(() => n(Date.now()), e);
18
+ return () => clearInterval(t);
19
+ }, [e]), t;
20
+ }
21
+ function w({ limit: n = 3, className: i }) {
22
+ let { navigateTo: a } = e(), o = C(), { data: s, isLoading: c, isError: u } = t({
23
+ status: "SCHEDULED",
24
+ pagination: { limit: x }
25
+ }), p = l(() => (s?.items ?? []).filter((e) => new Date(e.startsAt).getTime() >= o).slice(0, n), [
26
+ s,
27
+ o,
28
+ n
29
+ ]);
30
+ return c || u || p.length === 0 ? null : /* @__PURE__ */ y("section", {
31
+ className: r("mx-auto w-full max-w-2xl", i),
32
+ "aria-labelledby": "cc-upcoming",
33
+ children: [/* @__PURE__ */ y("div", {
34
+ className: "mb-2 flex items-center justify-between gap-3",
35
+ children: [/* @__PURE__ */ y("h2", {
36
+ id: "cc-upcoming",
37
+ className: "inline-flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-text-tertiary",
38
+ children: [/* @__PURE__ */ v(f, { className: "h-3.5 w-3.5" }), " Upcoming events"]
39
+ }), /* @__PURE__ */ y("button", {
40
+ type: "button",
41
+ onClick: () => a("events"),
42
+ className: "inline-flex items-center gap-1 text-xs font-medium text-text-secondary transition-colors hover:text-text-primary",
43
+ children: ["View all ", /* @__PURE__ */ v(d, { className: "h-3 w-3" })]
44
+ })]
45
+ }), /* @__PURE__ */ v("ul", {
46
+ className: "overflow-hidden rounded-2xl border border-border-seam bg-bg-surface",
47
+ children: p.map((e) => /* @__PURE__ */ v("li", {
48
+ className: "border-b border-border-seam last:border-b-0",
49
+ children: /* @__PURE__ */ v(T, { event: e })
50
+ }, e.id))
51
+ })]
52
+ });
53
+ }
54
+ function T({ event: t }) {
55
+ let { navigateTo: c } = e(), l = n(), [d, x] = u(null), S = d === "GOING", C = t.mode === "IN_PERSON" ? m : g, w = t.mode === "IN_PERSON" ? t.location || "In person" : t.mode === "HYBRID" ? `Hybrid${t.location ? ` · ${t.location}` : ""}` : "Online", T = async () => {
56
+ try {
57
+ await l.mutateAsync({
58
+ eventId: t.id,
59
+ status: "GOING"
60
+ }), x("GOING"), a.success("RSVP: Going", t.title);
61
+ } catch (e) {
62
+ a.error("Could not RSVP", i(e));
63
+ }
64
+ };
65
+ return /* @__PURE__ */ y("div", {
66
+ className: "flex items-center gap-3 px-4 py-3",
67
+ children: [
68
+ /* @__PURE__ */ v("span", {
69
+ "aria-hidden": "true",
70
+ className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-accent",
71
+ children: /* @__PURE__ */ v(f, { className: "h-5 w-5" })
72
+ }),
73
+ /* @__PURE__ */ y("div", {
74
+ className: "min-w-0 flex-1",
75
+ children: [/* @__PURE__ */ v("button", {
76
+ type: "button",
77
+ onClick: () => c(`events/${t.id}`),
78
+ className: "block max-w-full truncate text-left text-sm font-semibold text-text-primary transition-colors hover:text-accent",
79
+ children: t.title
80
+ }), /* @__PURE__ */ y("div", {
81
+ className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-text-tertiary",
82
+ children: [
83
+ /* @__PURE__ */ v("span", { children: o(t.startsAt) }),
84
+ /* @__PURE__ */ v("span", {
85
+ "aria-hidden": "true",
86
+ children: "·"
87
+ }),
88
+ /* @__PURE__ */ y("span", {
89
+ className: "inline-flex items-center gap-1",
90
+ children: [
91
+ /* @__PURE__ */ v(C, { className: "h-3 w-3" }),
92
+ " ",
93
+ w
94
+ ]
95
+ }),
96
+ /* @__PURE__ */ v("span", {
97
+ "aria-hidden": "true",
98
+ children: "·"
99
+ }),
100
+ /* @__PURE__ */ y("span", {
101
+ className: "inline-flex items-center gap-1",
102
+ children: [
103
+ /* @__PURE__ */ v(h, { className: "h-3 w-3" }),
104
+ s(t.rsvpCount),
105
+ t.capacity ? ` / ${s(t.capacity)}` : ""
106
+ ]
107
+ })
108
+ ]
109
+ })]
110
+ }),
111
+ /* @__PURE__ */ v(b, {
112
+ size: "sm",
113
+ variant: S ? "outline" : "default",
114
+ onClick: () => void T(),
115
+ disabled: l.isPending,
116
+ "aria-pressed": S,
117
+ className: r("shrink-0", S && "text-accent"),
118
+ children: S ? /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(p, { className: "mr-1 h-3.5 w-3.5" }), " Going"] }) : "RSVP"
119
+ })
120
+ ]
121
+ });
122
+ }
123
+ //#endregion
124
+ export { w as UpcomingEvents };
125
+
126
+ //# sourceMappingURL=UpcomingEvents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpcomingEvents.js","names":[],"sources":["../../../src/comradecircle/components/UpcomingEvents.tsx"],"sourcesContent":["/**\n * UpcomingEvents — a compact \"what's next\" strip for the home feed.\n * @module comradecircle/components\n *\n * Reads `comListEvents(status: SCHEDULED)` — the same root field the Events page\n * already uses — and writes `comRsvpEvent(eventId, status)` straight from the\n * row, so the member can commit to the next gathering without leaving home.\n * Every value shown (`title`, `startsAt`, `mode`, `location`, `rsvpCount`,\n * `capacity`) comes from that query's own selection set.\n *\n * Why the client-side date filter: the svc orders `comListEvents` by\n * `startsAt: 'asc'` and does NOT drop events whose start time has passed, so an\n * event left in `SCHEDULED` after its slot sorts *ahead* of genuinely upcoming\n * ones. The rail therefore reads a window of the ordered list and keeps only\n * rows still in the future. If that window is entirely historical the strip\n * renders nothing at all — it never claims \"no upcoming events\", because one\n * page of a list is not evidence that none exist.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { ArrowRight, CalendarDays, Check, MapPin, Users, Video } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComEvents, useComRsvpEvent } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { formatDateTime, formatNumber } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComEvent, ComRsvpStatus } from '../types';\n\n/** How far down the `startsAt`-ascending list to look for future events. */\nconst LOOKAHEAD = 50;\n\n/** How often the \"is it still in the future?\" cut-off is re-evaluated. */\nconst TICK_MS = 60_000;\n\n/**\n * A wall-clock reading that advances on its own, so a row whose start time\n * passes while the page is open drops out without waiting for a refetch.\n * Reading the clock in an effect (never in render) keeps the component pure.\n */\nfunction useNow(intervalMs: number = TICK_MS): number {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n const id = setInterval(() => setNow(Date.now()), intervalMs);\n return () => clearInterval(id);\n }, [intervalMs]);\n return now;\n}\n\ninterface UpcomingEventsProps {\n /** Rows to render once past events are filtered out. */\n limit?: number;\n className?: string;\n}\n\nexport function UpcomingEvents({ limit = 3, className }: UpcomingEventsProps) {\n const { navigateTo } = useComradeCircle();\n const now = useNow();\n const { data, isLoading, isError } = useComEvents({\n status: 'SCHEDULED',\n pagination: { limit: LOOKAHEAD },\n });\n\n const upcoming = useMemo(\n () =>\n (data?.items ?? [])\n .filter((event) => new Date(event.startsAt).getTime() >= now)\n .slice(0, limit),\n [data, now, limit]\n );\n\n // A supplementary strip above the feed: while it loads, or if it fails, the\n // feed below is the page — the rail simply stays out of the layout rather\n // than pushing the posts down for a section that may turn out to be empty.\n if (isLoading || isError || upcoming.length === 0) return null;\n\n return (\n <section className={cn('mx-auto w-full max-w-2xl', className)} aria-labelledby=\"cc-upcoming\">\n <div className=\"mb-2 flex items-center justify-between gap-3\">\n <h2\n id=\"cc-upcoming\"\n className=\"inline-flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-text-tertiary\"\n >\n <CalendarDays className=\"h-3.5 w-3.5\" /> Upcoming events\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('events')}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n View all <ArrowRight className=\"h-3 w-3\" />\n </button>\n </div>\n\n <ul className=\"overflow-hidden rounded-2xl border border-border-seam bg-bg-surface\">\n {upcoming.map((event) => (\n <li key={event.id} className=\"border-b border-border-seam last:border-b-0\">\n <UpcomingEventRow event={event} />\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\nfunction UpcomingEventRow({ event }: { event: ComEvent }) {\n const { navigateTo } = useComradeCircle();\n const rsvp = useComRsvpEvent();\n // `comListEvents` returns aggregate `rsvpCount`, not the caller's own RSVP, so\n // the confirmed cue reflects this session's mutation only — the same contract\n // EventCard documents.\n const [myStatus, setMyStatus] = useState<ComRsvpStatus | null>(null);\n\n const going = myStatus === 'GOING';\n const ModeIcon = event.mode === 'IN_PERSON' ? MapPin : Video;\n const where =\n event.mode === 'IN_PERSON'\n ? event.location || 'In person'\n : event.mode === 'HYBRID'\n ? `Hybrid${event.location ? ` · ${event.location}` : ''}`\n : 'Online';\n\n const doRsvp = async () => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status: 'GOING' });\n setMyStatus('GOING');\n notify.success('RSVP: Going', event.title);\n } catch (err) {\n notify.error('Could not RSVP', describeError(err));\n }\n };\n\n return (\n <div className=\"flex items-center gap-3 px-4 py-3\">\n <span\n aria-hidden=\"true\"\n className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-accent\"\n >\n <CalendarDays className=\"h-5 w-5\" />\n </span>\n\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`events/${event.id}`)}\n className=\"block max-w-full truncate text-left text-sm font-semibold text-text-primary transition-colors hover:text-accent\"\n >\n {event.title}\n </button>\n <div className=\"mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-text-tertiary\">\n <span>{formatDateTime(event.startsAt)}</span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"inline-flex items-center gap-1\">\n <ModeIcon className=\"h-3 w-3\" /> {where}\n </span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"inline-flex items-center gap-1\">\n <Users className=\"h-3 w-3\" />\n {formatNumber(event.rsvpCount)}\n {event.capacity ? ` / ${formatNumber(event.capacity)}` : ''}\n </span>\n </div>\n </div>\n\n <Button\n size=\"sm\"\n variant={going ? 'outline' : 'default'}\n onClick={() => void doRsvp()}\n disabled={rsvp.isPending}\n aria-pressed={going}\n className={cn('shrink-0', going && 'text-accent')}\n >\n {going ? (\n <>\n <Check className=\"mr-1 h-3.5 w-3.5\" /> Going\n </>\n ) : (\n 'RSVP'\n )}\n </Button>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA8BA,IAAM,IAAY,IAGZ,IAAU;AAOhB,SAAS,EAAO,IAAqB,GAAiB;CACpD,IAAM,CAAC,GAAK,KAAU,QAAe,KAAK,IAAI,CAAC;CAK/C,OAJA,QAAgB;EACd,IAAM,IAAK,kBAAkB,EAAO,KAAK,IAAI,CAAC,GAAG,CAAU;EAC3D,aAAa,cAAc,CAAE;CAC/B,GAAG,CAAC,CAAU,CAAC,GACR;AACT;AAQA,SAAgB,EAAe,EAAE,WAAQ,GAAG,gBAAkC;CAC5E,IAAM,EAAE,kBAAe,EAAiB,GAClC,IAAM,EAAO,GACb,EAAE,SAAM,cAAW,eAAY,EAAa;EAChD,QAAQ;EACR,YAAY,EAAE,OAAO,EAAU;CACjC,CAAC,GAEK,IAAW,SAEZ,GAAM,SAAS,CAAC,GACd,QAAQ,MAAU,IAAI,KAAK,EAAM,QAAQ,EAAE,QAAQ,KAAK,CAAG,EAC3D,MAAM,GAAG,CAAK,GACnB;EAAC;EAAM;EAAK;CAAK,CACnB;CAOA,OAFI,KAAa,KAAW,EAAS,WAAW,IAAU,OAGxD,kBAAC,WAAD;EAAS,WAAW,EAAG,4BAA4B,CAAS;EAAG,mBAAgB;YAA/E,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IACE,IAAG;IACH,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAc,WAAU,cAAe,CAAA,GAAC,kBACtC;OACJ,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,QAAQ;IAClC,WAAU;cAHZ,CAIC,aACU,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA,CACpC;KACL;MAEL,kBAAC,MAAD;GAAI,WAAU;aACX,EAAS,KAAK,MACb,kBAAC,MAAD;IAAmB,WAAU;cAC3B,kBAAC,GAAD,EAAyB,SAAQ,CAAA;GAC/B,GAFK,EAAM,EAEX,CACL;EACC,CAAA,CACG;;AAEb;AAEA,SAAS,EAAiB,EAAE,YAA8B;CACxD,IAAM,EAAE,kBAAe,EAAiB,GAClC,IAAO,EAAgB,GAIvB,CAAC,GAAU,KAAe,EAA+B,IAAI,GAE7D,IAAQ,MAAa,SACrB,IAAW,EAAM,SAAS,cAAc,IAAS,GACjD,IACJ,EAAM,SAAS,cACX,EAAM,YAAY,cAClB,EAAM,SAAS,WACb,SAAS,EAAM,WAAW,MAAM,EAAM,aAAa,OACnD,UAEF,IAAS,YAAY;EACzB,IAAI;GAGF,AAFA,MAAM,EAAK,YAAY;IAAE,SAAS,EAAM;IAAI,QAAQ;GAAQ,CAAC,GAC7D,EAAY,OAAO,GACnB,EAAO,QAAQ,eAAe,EAAM,KAAK;EAC3C,SAAS,GAAK;GACZ,EAAO,MAAM,kBAAkB,EAAc,CAAG,CAAC;EACnD;CACF;CAEA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IACE,eAAY;IACZ,WAAU;cAEV,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;GAC/B,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,UAAU,EAAM,IAAI;KAC9C,WAAU;eAET,EAAM;IACD,CAAA,GACR,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAM,QAAQ,EAAQ,CAAA;MAC5C,kBAAC,QAAD;OAAM,eAAY;iBAAO;MAAO,CAAA;MAChC,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;QAAC;QAAE;OAC9B;;MACN,kBAAC,QAAD;OAAM,eAAY;iBAAO;MAAO,CAAA;MAChC,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;QAC3B,EAAa,EAAM,SAAS;QAC5B,EAAM,WAAW,MAAM,EAAa,EAAM,QAAQ,MAAM;OACrD;;KACH;MACF;;GAEL,kBAAC,GAAD;IACE,MAAK;IACL,SAAS,IAAQ,YAAY;IAC7B,eAAe,KAAK,EAAO;IAC3B,UAAU,EAAK;IACf,gBAAc;IACd,WAAW,EAAG,YAAY,KAAS,aAAa;cAE/C,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAO,WAAU,mBAAoB,CAAA,GAAC,QACtC,EAAA,CAAA,IAEF;GAEI,CAAA;EACL;;AAET"}
@@ -22,7 +22,9 @@ export { TagInput } from './TagInput';
22
22
  export { ReactionBar } from './ReactionBar';
23
23
  export { CommentThread } from './CommentThread';
24
24
  export { PostComposer } from './PostComposer';
25
+ export { DraftsPanel } from './DraftsPanel';
25
26
  export { PostCard } from './PostCard';
27
+ export { UpcomingEvents } from './UpcomingEvents';
26
28
  export { CircleCard } from './CircleCard';
27
29
  export { EventCard } from './EventCard';
28
30
  export { PollCard } from './PollCard';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -5,4 +5,5 @@
5
5
  export * from './useComradeCircleApi';
6
6
  export * from './useCursorPager';
7
7
  export * from './useFiles';
8
+ export * from './useTr';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC"}
@@ -1,3 +1,4 @@
1
1
  import "./useComradeCircleApi.js";
2
2
  import "./useCursorPager.js";
3
3
  import "./useFiles.js";
4
+ import "./useTr.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * useTr — translation hook with fallback semantics.
3
+ *
4
+ * Wraps `useI18n().t()` so that when a key has no translation (i.e. the
5
+ * provider returns the key itself), the caller's English fallback is used
6
+ * instead. This keeps consumer call sites consistent and avoids leaking
7
+ * untranslated keys into the UI.
8
+ *
9
+ * Supports optional interpolation params using `{{key}}` placeholders.
10
+ *
11
+ * @example
12
+ * const tr = useTr();
13
+ * return <h1>{tr('comradecircle.pages.feed.title', 'Home')}</h1>;
14
+ * return <p>{tr('comradecircle.components.pagination.showing', 'Showing {{from}}–{{to}}', { from: 1, to: 10 })}</p>;
15
+ */
16
+ export type TranslationParams = Record<string, string | number>;
17
+ export declare function useTr(): (key: string, fallback: string, params?: TranslationParams) => string;
18
+ //# sourceMappingURL=useTr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/hooks/useTr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEhE,wBAAgB,KAAK,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,KAAK,MAAM,CAQ7F"}
@@ -0,0 +1,12 @@
1
+ import { tWithFallback as e } from "../utils/i18n.js";
2
+ import { useCallback as t } from "react";
3
+ import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
+ //#region src/comradecircle/hooks/useTr.ts
5
+ function r() {
6
+ let { t: r } = n();
7
+ return t((t, n, i) => e(r, t, n, i), [r]);
8
+ }
9
+ //#endregion
10
+ export { r as useTr };
11
+
12
+ //# sourceMappingURL=useTr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.js","names":[],"sources":["../../../src/comradecircle/hooks/useTr.ts"],"sourcesContent":["/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('comradecircle.pages.feed.title', 'Home')}</h1>;\n * return <p>{tr('comradecircle.components.pagination.showing', 'Showing {{from}}–{{to}}', { from: 1, to: 10 })}</p>;\n */\n\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\nexport type TranslationParams = Record<string, string | number>;\n\nexport function useTr(): (key: string, fallback: string, params?: TranslationParams) => string {\n const { t } = useI18n();\n\n return useCallback(\n (key: string, fallback: string, params?: TranslationParams): string =>\n tWithFallback(t, key, fallback, params),\n [t]\n );\n}\n"],"mappings":";;;;AAsBA,SAAgB,IAA+E;CAC7F,IAAM,EAAE,SAAM,EAAQ;CAEtB,OAAO,GACJ,GAAa,GAAkB,MAC9B,EAAc,GAAG,GAAK,GAAU,CAAM,GACxC,CAAC,CAAC,CACJ;AACF"}
@@ -5,6 +5,11 @@
5
5
  * Reads `comMyFeed` (published posts from every channel, member, and circle the
6
6
  * caller follows, newest first). A new member with no follows sees a guided
7
7
  * empty state pointing at circle & member discovery.
8
+ *
9
+ * Above the post column sits {@link UpcomingEvents} — the mobile home mock
10
+ * shows the home surface carrying the member's next gathering (with an RSVP
11
+ * affordance), not posts alone. It is a separate `comListEvents` read because
12
+ * `comMyFeed` returns `ComPost` rows only.
8
13
  */
9
14
  export declare function FeedPage(): import("react/jsx-runtime").JSX.Element;
10
15
  //# sourceMappingURL=FeedPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FeedPage.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/pages/FeedPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiEH,wBAAgB,QAAQ,4CAiEvB"}
1
+ {"version":3,"file":"FeedPage.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/pages/FeedPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAkEH,wBAAgB,QAAQ,4CAgFvB"}