@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,176 +1,189 @@
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 { useComRemovePost as n, useComUpdatePost as r } from "../hooks/useComradeCircleApi.js";
3
+ import { useComRemovePost as t, useComUpdatePost as n } from "../hooks/useComradeCircleApi.js";
4
+ import { useTr as r } from "../hooks/useTr.js";
5
5
  import "../hooks/index.js";
6
- import { MemberIdentity as i } from "./MemberIdentity.js";
7
- import { describeError as a, notify as o } from "../lib/toast.js";
8
- import { ReactionBar as s } from "./ReactionBar.js";
9
- import { formatRelativeTime as c } from "../utils/formatters.js";
10
- import { CommentThread as l } from "./CommentThread.js";
11
- import { AttachmentGrid as u } from "./ImageAttachments.js";
12
- import { useState as d } from "react";
13
- import { ArrowUpRight as f, MessageSquare as p, MoreHorizontal as m, Pencil as h, Pin as g, Trash2 as _ } from "lucide-react";
14
- import { jsx as v, jsxs as y } from "react/jsx-runtime";
15
- import { Button as b, DropdownMenu as x, DropdownMenuContent as S, DropdownMenuItem as C, DropdownMenuSeparator as w, DropdownMenuTrigger as T, GlassCard as E, Textarea as D } from "@burdenoff/fe-libs/ui";
6
+ import { cn as i } from "../utils/cn.js";
7
+ import { MemberIdentity as a } from "./MemberIdentity.js";
8
+ import { describeError as o, notify as s } from "../lib/toast.js";
9
+ import { ReactionBar as c } from "./ReactionBar.js";
10
+ import { formatRelativeTime as l } from "../utils/formatters.js";
11
+ import { CommentThread as u } from "./CommentThread.js";
12
+ import { AttachmentGrid as d } from "./ImageAttachments.js";
13
+ import { useState as f } from "react";
14
+ import { ArrowUpRight as p, MessageSquare as m, MoreHorizontal as h, Pencil as g, Pin as _, Trash2 as v } from "lucide-react";
15
+ import { jsx as y, jsxs as b } from "react/jsx-runtime";
16
+ import { Button as x, DropdownMenu as S, DropdownMenuContent as C, DropdownMenuItem as w, DropdownMenuSeparator as T, DropdownMenuTrigger as E, GlassCard as D, Textarea as O } from "@burdenoff/fe-libs/ui";
16
17
  //#region src/comradecircle/components/PostCard.tsx
17
- function O({ post: O, defaultExpanded: k = !1, hideThreadLink: A = !1 }) {
18
- let { currentUser: j, navigateTo: M } = e(), N = r(), P = n(), [F, I] = d(k), [L, R] = d(!1), [z, B] = d(O.body), V = !!(j?.id && j.id === O.authorId);
19
- return /* @__PURE__ */ y(E, {
18
+ function k({ post: k, defaultExpanded: A = !1, hideThreadLink: j = !1 }) {
19
+ let { currentUser: M, navigateTo: N } = e(), P = n(), F = t(), I = r(), [L, R] = f(A), [z, B] = f(!1), [V, H] = f(k.body), U = !!(M?.id && M.id === k.authorId);
20
+ return /* @__PURE__ */ b(D, {
20
21
  treatment: "plain",
21
- className: t("p-4 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]", O.isPinned && "ring-1 ring-accent/40"),
22
+ className: i("p-4 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]", k.isPinned && "ring-1 ring-accent/40"),
22
23
  children: [
23
- /* @__PURE__ */ y("div", {
24
+ /* @__PURE__ */ b("div", {
24
25
  className: "flex items-start justify-between gap-3",
25
- children: [/* @__PURE__ */ v(i, {
26
- memberId: O.authorId,
26
+ children: [/* @__PURE__ */ y(a, {
27
+ memberId: k.authorId,
27
28
  variant: "row",
28
29
  size: "md",
29
- suffix: c(O.createdAt),
30
+ suffix: l(k.createdAt),
30
31
  showPresence: !0
31
- }), /* @__PURE__ */ y("div", {
32
+ }), /* @__PURE__ */ b("div", {
32
33
  className: "flex items-center gap-1",
33
- children: [O.isPinned && /* @__PURE__ */ y("span", {
34
+ children: [k.isPinned && /* @__PURE__ */ b("span", {
34
35
  className: "inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary",
35
- children: [/* @__PURE__ */ v(g, { className: "h-3 w-3" }), " Pinned"]
36
- }), V && /* @__PURE__ */ y(x, { children: [/* @__PURE__ */ v(T, {
36
+ children: [
37
+ /* @__PURE__ */ y(_, { className: "h-3 w-3" }),
38
+ " ",
39
+ I("comradecircle.components.postCard.pinned", "Pinned")
40
+ ]
41
+ }), U && /* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(E, {
37
42
  asChild: !0,
38
- children: /* @__PURE__ */ v("button", {
43
+ children: /* @__PURE__ */ y("button", {
39
44
  type: "button",
40
- "aria-label": "Post actions",
45
+ "aria-label": I("comradecircle.components.postCard.postActionsAriaLabel", "Post actions"),
41
46
  className: "rounded-lg p-1.5 text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary",
42
- children: /* @__PURE__ */ v(m, { className: "h-4 w-4" })
47
+ children: /* @__PURE__ */ y(h, { className: "h-4 w-4" })
43
48
  })
44
- }), /* @__PURE__ */ y(S, {
49
+ }), /* @__PURE__ */ b(C, {
45
50
  align: "end",
46
51
  children: [
47
- /* @__PURE__ */ y(C, {
48
- onClick: () => R(!0),
49
- children: [/* @__PURE__ */ v(h, { className: "mr-2 h-4 w-4" }), " Edit"]
52
+ /* @__PURE__ */ b(w, {
53
+ onClick: () => B(!0),
54
+ children: [
55
+ /* @__PURE__ */ y(g, { className: "mr-2 h-4 w-4" }),
56
+ " ",
57
+ I("comradecircle.components.postCard.edit", "Edit")
58
+ ]
50
59
  }),
51
- /* @__PURE__ */ y(C, {
60
+ /* @__PURE__ */ b(w, {
52
61
  onClick: async () => {
53
62
  try {
54
- await N.mutateAsync({
55
- id: O.id,
56
- input: { isPinned: !O.isPinned }
57
- }), o.success(O.isPinned ? "Post unpinned" : "Post pinned");
63
+ await P.mutateAsync({
64
+ id: k.id,
65
+ input: { isPinned: !k.isPinned }
66
+ }), s.success(k.isPinned ? I("comradecircle.components.postCard.unpinnedSuccess", "Post unpinned") : I("comradecircle.components.postCard.pinnedSuccess", "Post pinned"));
58
67
  } catch (e) {
59
- o.error("Could not update post", a(e));
68
+ s.error(I("comradecircle.components.postCard.pinError", "Could not update post"), o(e));
60
69
  }
61
70
  },
62
71
  children: [
63
- /* @__PURE__ */ v(g, { className: "mr-2 h-4 w-4" }),
72
+ /* @__PURE__ */ y(_, { className: "mr-2 h-4 w-4" }),
64
73
  " ",
65
- O.isPinned ? "Unpin" : "Pin"
74
+ k.isPinned ? I("comradecircle.components.postCard.unpin", "Unpin") : I("comradecircle.components.postCard.pin", "Pin")
66
75
  ]
67
76
  }),
68
- /* @__PURE__ */ v(w, {}),
69
- /* @__PURE__ */ y(C, {
77
+ /* @__PURE__ */ y(T, {}),
78
+ /* @__PURE__ */ b(w, {
70
79
  onClick: async () => {
71
80
  try {
72
- await P.mutateAsync({
73
- id: O.id,
74
- channelId: O.channelId
75
- }), o.success("Post removed");
81
+ await F.mutateAsync({
82
+ id: k.id,
83
+ channelId: k.channelId
84
+ }), s.success(I("comradecircle.components.postCard.removedSuccess", "Post removed"));
76
85
  } catch (e) {
77
- o.error("Could not remove post", a(e));
86
+ s.error(I("comradecircle.components.postCard.removeError", "Could not remove post"), o(e));
78
87
  }
79
88
  },
80
89
  className: "text-status-error-text focus:text-status-error-text",
81
- children: [/* @__PURE__ */ v(_, { className: "mr-2 h-4 w-4" }), " Delete"]
90
+ children: [
91
+ /* @__PURE__ */ y(v, { className: "mr-2 h-4 w-4" }),
92
+ " ",
93
+ I("comradecircle.components.postCard.delete", "Delete")
94
+ ]
82
95
  })
83
96
  ]
84
97
  })] })]
85
98
  })]
86
99
  }),
87
- L ? /* @__PURE__ */ y("div", {
100
+ z ? /* @__PURE__ */ b("div", {
88
101
  className: "mt-3",
89
- children: [/* @__PURE__ */ v(D, {
90
- value: z,
91
- onChange: (e) => B(e.target.value),
102
+ children: [/* @__PURE__ */ y(O, {
103
+ value: V,
104
+ onChange: (e) => H(e.target.value),
92
105
  rows: 3,
93
106
  className: "resize-none"
94
- }), /* @__PURE__ */ y("div", {
107
+ }), /* @__PURE__ */ b("div", {
95
108
  className: "mt-2 flex justify-end gap-2",
96
- children: [/* @__PURE__ */ v(b, {
109
+ children: [/* @__PURE__ */ y(x, {
97
110
  variant: "outline",
98
111
  size: "sm",
99
112
  onClick: () => {
100
- B(O.body), R(!1);
113
+ H(k.body), B(!1);
101
114
  },
102
- children: "Cancel"
103
- }), /* @__PURE__ */ v(b, {
115
+ children: I("comradecircle.components.postCard.cancel", "Cancel")
116
+ }), /* @__PURE__ */ y(x, {
104
117
  size: "sm",
105
118
  onClick: async () => {
106
- let e = z.trim();
107
- if (!e || e === O.body) {
108
- R(!1);
119
+ let e = V.trim();
120
+ if (!e || e === k.body) {
121
+ B(!1);
109
122
  return;
110
123
  }
111
124
  try {
112
- await N.mutateAsync({
113
- id: O.id,
125
+ await P.mutateAsync({
126
+ id: k.id,
114
127
  input: { body: e }
115
- }), R(!1), o.success("Post updated");
128
+ }), B(!1), s.success(I("comradecircle.components.postCard.updatedSuccess", "Post updated"));
116
129
  } catch (e) {
117
- o.error("Could not update post", a(e));
130
+ s.error(I("comradecircle.components.postCard.updateError", "Could not update post"), o(e));
118
131
  }
119
132
  },
120
- disabled: N.isPending,
121
- children: "Save"
133
+ disabled: P.isPending,
134
+ children: I("comradecircle.components.postCard.save", "Save")
122
135
  })]
123
136
  })]
124
- }) : /* @__PURE__ */ v("p", {
137
+ }) : /* @__PURE__ */ y("p", {
125
138
  className: "mt-3 whitespace-pre-wrap break-words text-sm text-text-primary",
126
- children: O.body
139
+ children: k.body
127
140
  }),
128
- O.tags.length > 0 && /* @__PURE__ */ v("div", {
141
+ k.tags.length > 0 && /* @__PURE__ */ y("div", {
129
142
  className: "mt-2 flex flex-wrap gap-1.5",
130
- children: O.tags.map((e) => /* @__PURE__ */ y("span", {
143
+ children: k.tags.map((e) => /* @__PURE__ */ b("span", {
131
144
  className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary",
132
145
  children: ["#", e]
133
146
  }, e))
134
147
  }),
135
- O.attachmentFileIds.length > 0 && /* @__PURE__ */ v(u, { fileIds: O.attachmentFileIds }),
136
- /* @__PURE__ */ y("div", {
148
+ k.attachmentFileIds.length > 0 && /* @__PURE__ */ y(d, { fileIds: k.attachmentFileIds }),
149
+ /* @__PURE__ */ b("div", {
137
150
  className: "mt-3 flex items-center justify-between gap-3 border-t border-border-seam pt-3",
138
- children: [/* @__PURE__ */ v(s, { post: O }), /* @__PURE__ */ y("div", {
151
+ children: [/* @__PURE__ */ y(c, { post: k }), /* @__PURE__ */ b("div", {
139
152
  className: "flex items-center gap-1",
140
- children: [/* @__PURE__ */ y("button", {
153
+ children: [/* @__PURE__ */ b("button", {
141
154
  type: "button",
142
- onClick: () => I((e) => !e),
143
- "aria-expanded": F,
144
- className: t("inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors", F ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
155
+ onClick: () => R((e) => !e),
156
+ "aria-expanded": L,
157
+ className: i("inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors", L ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
145
158
  children: [
146
- /* @__PURE__ */ v(p, { className: "h-3.5 w-3.5" }),
147
- /* @__PURE__ */ v("span", {
159
+ /* @__PURE__ */ y(m, { className: "h-3.5 w-3.5" }),
160
+ /* @__PURE__ */ y("span", {
148
161
  className: "tabular-nums",
149
- children: O.commentCount
162
+ children: k.commentCount
150
163
  }),
151
- /* @__PURE__ */ v("span", {
164
+ /* @__PURE__ */ y("span", {
152
165
  className: "hidden sm:inline",
153
- children: O.commentCount === 1 ? "comment" : "comments"
166
+ children: k.commentCount === 1 ? I("comradecircle.components.postCard.comment", "comment") : I("comradecircle.components.postCard.comments", "comments")
154
167
  })
155
168
  ]
156
- }), !A && /* @__PURE__ */ y("button", {
169
+ }), !j && /* @__PURE__ */ b("button", {
157
170
  type: "button",
158
- onClick: () => M(`posts/${O.id}`),
159
- "aria-label": "Open thread",
160
- title: "Open thread",
171
+ onClick: () => N(`posts/${k.id}`),
172
+ "aria-label": I("comradecircle.components.postCard.openThreadAriaLabel", "Open thread"),
173
+ title: I("comradecircle.components.postCard.openThreadTitle", "Open thread"),
161
174
  className: "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary",
162
- children: [/* @__PURE__ */ v(f, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ v("span", {
175
+ children: [/* @__PURE__ */ y(p, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ y("span", {
163
176
  className: "hidden sm:inline",
164
- children: "Open thread"
177
+ children: I("comradecircle.components.postCard.openThread", "Open thread")
165
178
  })]
166
179
  })]
167
180
  })]
168
181
  }),
169
- F && /* @__PURE__ */ v(l, { postId: O.id })
182
+ L && /* @__PURE__ */ y(u, { postId: k.id })
170
183
  ]
171
184
  });
172
185
  }
173
186
  //#endregion
174
- export { O as PostCard };
187
+ export { k as PostCard };
175
188
 
176
189
  //# sourceMappingURL=PostCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostCard.js","names":[],"sources":["../../../src/comradecircle/components/PostCard.tsx"],"sourcesContent":["/**\n * PostCard — a single post with reactions and a collapsible comment thread.\n * @module comradecircle/components\n *\n * Reads its data from a `ComPost`; writes via `comUpdatePost` (edit / pin) and\n * `comRemovePost` (delete). Reactions + comments are delegated to\n * {@link ReactionBar} and {@link CommentThread}. Author-only actions (edit, pin,\n * delete) are gated on `authorId === currentUser.id`.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, MoreHorizontal, Pin, Pencil, Trash2, ArrowUpRight } from 'lucide-react';\nimport {\n GlassCard,\n Button,\n Textarea,\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MemberIdentity } from './MemberIdentity';\nimport { ReactionBar } from './ReactionBar';\nimport { CommentThread } from './CommentThread';\nimport { AttachmentGrid } from './ImageAttachments';\nimport { useComUpdatePost, useComRemovePost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost } from '../types';\n\ninterface PostCardProps {\n post: ComPost;\n /** Start with the comment thread open. */\n defaultExpanded?: boolean;\n /** Hide the \"open thread\" link — set when already rendered on the thread page itself. */\n hideThreadLink?: boolean;\n}\n\nexport function PostCard({ post, defaultExpanded = false, hideThreadLink = false }: PostCardProps) {\n const { currentUser, navigateTo } = useComradeCircle();\n const updatePost = useComUpdatePost();\n const removePost = useComRemovePost();\n const [expanded, setExpanded] = useState(defaultExpanded);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(post.body);\n\n const isAuthor = Boolean(currentUser?.id && currentUser.id === post.authorId);\n\n const saveEdit = async () => {\n const text = draft.trim();\n if (!text || text === post.body) {\n setEditing(false);\n return;\n }\n try {\n await updatePost.mutateAsync({ id: post.id, input: { body: text } });\n setEditing(false);\n notify.success('Post updated');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const togglePin = async () => {\n try {\n await updatePost.mutateAsync({ id: post.id, input: { isPinned: !post.isPinned } });\n notify.success(post.isPinned ? 'Post unpinned' : 'Post pinned');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const remove = async () => {\n try {\n await removePost.mutateAsync({ id: post.id, channelId: post.channelId });\n notify.success('Post removed');\n } catch (err) {\n notify.error('Could not remove post', describeError(err));\n }\n };\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'p-4 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]',\n post.isPinned && 'ring-1 ring-accent/40'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <MemberIdentity\n memberId={post.authorId}\n variant=\"row\"\n size=\"md\"\n suffix={formatRelativeTime(post.createdAt)}\n showPresence\n />\n <div className=\"flex items-center gap-1\">\n {post.isPinned && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n <Pin className=\"h-3 w-3\" /> Pinned\n </span>\n )}\n {isAuthor && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Post actions\"\n className=\"rounded-lg p-1.5 text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setEditing(true)}>\n <Pencil className=\"mr-2 h-4 w-4\" /> Edit\n </DropdownMenuItem>\n <DropdownMenuItem onClick={togglePin}>\n <Pin className=\"mr-2 h-4 w-4\" /> {post.isPinned ? 'Unpin' : 'Pin'}\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={remove}\n className=\"text-status-error-text focus:text-status-error-text\"\n >\n <Trash2 className=\"mr-2 h-4 w-4\" /> Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n\n {editing ? (\n <div className=\"mt-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n rows={3}\n className=\"resize-none\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n setDraft(post.body);\n setEditing(false);\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" onClick={saveEdit} disabled={updatePost.isPending}>\n Save\n </Button>\n </div>\n </div>\n ) : (\n <p className=\"mt-3 whitespace-pre-wrap break-words text-sm text-text-primary\">\n {post.body}\n </p>\n )}\n\n {post.tags.length > 0 && (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {post.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {post.attachmentFileIds.length > 0 && <AttachmentGrid fileIds={post.attachmentFileIds} />}\n\n <div className=\"mt-3 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <ReactionBar post={post} />\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => setExpanded((s) => !s)}\n aria-expanded={expanded}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors',\n expanded\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <MessageSquare className=\"h-3.5 w-3.5\" />\n <span className=\"tabular-nums\">{post.commentCount}</span>\n <span className=\"hidden sm:inline\">\n {post.commentCount === 1 ? 'comment' : 'comments'}\n </span>\n </button>\n\n {!hideThreadLink && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`posts/${post.id}`)}\n aria-label=\"Open thread\"\n title=\"Open thread\"\n className=\"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5\" />\n <span className=\"hidden sm:inline\">Open thread</span>\n </button>\n )}\n </div>\n </div>\n\n {expanded && <CommentThread postId={post.id} />}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PostCard.js","names":[],"sources":["../../../src/comradecircle/components/PostCard.tsx"],"sourcesContent":["/**\n * PostCard — a single post with reactions and a collapsible comment thread.\n * @module comradecircle/components\n *\n * Reads its data from a `ComPost`; writes via `comUpdatePost` (edit / pin) and\n * `comRemovePost` (delete). Reactions + comments are delegated to\n * {@link ReactionBar} and {@link CommentThread}. Author-only actions (edit, pin,\n * delete) are gated on `authorId === currentUser.id`.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, MoreHorizontal, Pin, Pencil, Trash2, ArrowUpRight } from 'lucide-react';\nimport {\n GlassCard,\n Button,\n Textarea,\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MemberIdentity } from './MemberIdentity';\nimport { ReactionBar } from './ReactionBar';\nimport { CommentThread } from './CommentThread';\nimport { AttachmentGrid } from './ImageAttachments';\nimport { useComUpdatePost, useComRemovePost, useTr } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost } from '../types';\n\ninterface PostCardProps {\n post: ComPost;\n /** Start with the comment thread open. */\n defaultExpanded?: boolean;\n /** Hide the \"open thread\" link — set when already rendered on the thread page itself. */\n hideThreadLink?: boolean;\n}\n\nexport function PostCard({ post, defaultExpanded = false, hideThreadLink = false }: PostCardProps) {\n const { currentUser, navigateTo } = useComradeCircle();\n const updatePost = useComUpdatePost();\n const removePost = useComRemovePost();\n const tr = useTr();\n const [expanded, setExpanded] = useState(defaultExpanded);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(post.body);\n\n const isAuthor = Boolean(currentUser?.id && currentUser.id === post.authorId);\n\n const saveEdit = async () => {\n const text = draft.trim();\n if (!text || text === post.body) {\n setEditing(false);\n return;\n }\n try {\n await updatePost.mutateAsync({ id: post.id, input: { body: text } });\n setEditing(false);\n notify.success(tr('comradecircle.components.postCard.updatedSuccess', 'Post updated'));\n } catch (err) {\n notify.error(\n tr('comradecircle.components.postCard.updateError', 'Could not update post'),\n describeError(err)\n );\n }\n };\n\n const togglePin = async () => {\n try {\n await updatePost.mutateAsync({ id: post.id, input: { isPinned: !post.isPinned } });\n notify.success(\n post.isPinned\n ? tr('comradecircle.components.postCard.unpinnedSuccess', 'Post unpinned')\n : tr('comradecircle.components.postCard.pinnedSuccess', 'Post pinned')\n );\n } catch (err) {\n notify.error(\n tr('comradecircle.components.postCard.pinError', 'Could not update post'),\n describeError(err)\n );\n }\n };\n\n const remove = async () => {\n try {\n await removePost.mutateAsync({ id: post.id, channelId: post.channelId });\n notify.success(tr('comradecircle.components.postCard.removedSuccess', 'Post removed'));\n } catch (err) {\n notify.error(\n tr('comradecircle.components.postCard.removeError', 'Could not remove post'),\n describeError(err)\n );\n }\n };\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'p-4 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]',\n post.isPinned && 'ring-1 ring-accent/40'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <MemberIdentity\n memberId={post.authorId}\n variant=\"row\"\n size=\"md\"\n suffix={formatRelativeTime(post.createdAt)}\n showPresence\n />\n <div className=\"flex items-center gap-1\">\n {post.isPinned && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n <Pin className=\"h-3 w-3\" /> {tr('comradecircle.components.postCard.pinned', 'Pinned')}\n </span>\n )}\n {isAuthor && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label={tr(\n 'comradecircle.components.postCard.postActionsAriaLabel',\n 'Post actions'\n )}\n className=\"rounded-lg p-1.5 text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setEditing(true)}>\n <Pencil className=\"mr-2 h-4 w-4\" />{' '}\n {tr('comradecircle.components.postCard.edit', 'Edit')}\n </DropdownMenuItem>\n <DropdownMenuItem onClick={togglePin}>\n <Pin className=\"mr-2 h-4 w-4\" />{' '}\n {post.isPinned\n ? tr('comradecircle.components.postCard.unpin', 'Unpin')\n : tr('comradecircle.components.postCard.pin', 'Pin')}\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={remove}\n className=\"text-status-error-text focus:text-status-error-text\"\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />{' '}\n {tr('comradecircle.components.postCard.delete', 'Delete')}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n\n {editing ? (\n <div className=\"mt-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n rows={3}\n className=\"resize-none\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n setDraft(post.body);\n setEditing(false);\n }}\n >\n {tr('comradecircle.components.postCard.cancel', 'Cancel')}\n </Button>\n <Button size=\"sm\" onClick={saveEdit} disabled={updatePost.isPending}>\n {tr('comradecircle.components.postCard.save', 'Save')}\n </Button>\n </div>\n </div>\n ) : (\n <p className=\"mt-3 whitespace-pre-wrap break-words text-sm text-text-primary\">\n {post.body}\n </p>\n )}\n\n {post.tags.length > 0 && (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {post.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {post.attachmentFileIds.length > 0 && <AttachmentGrid fileIds={post.attachmentFileIds} />}\n\n <div className=\"mt-3 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <ReactionBar post={post} />\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => setExpanded((s) => !s)}\n aria-expanded={expanded}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors',\n expanded\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <MessageSquare className=\"h-3.5 w-3.5\" />\n <span className=\"tabular-nums\">{post.commentCount}</span>\n <span className=\"hidden sm:inline\">\n {post.commentCount === 1\n ? tr('comradecircle.components.postCard.comment', 'comment')\n : tr('comradecircle.components.postCard.comments', 'comments')}\n </span>\n </button>\n\n {!hideThreadLink && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`posts/${post.id}`)}\n aria-label={tr(\n 'comradecircle.components.postCard.openThreadAriaLabel',\n 'Open thread'\n )}\n title={tr('comradecircle.components.postCard.openThreadTitle', 'Open thread')}\n className=\"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5\" />\n <span className=\"hidden sm:inline\">\n {tr('comradecircle.components.postCard.openThread', 'Open thread')}\n </span>\n </button>\n )}\n </div>\n </div>\n\n {expanded && <CommentThread postId={post.id} />}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -1,15 +1,21 @@
1
1
  /**
2
- * PostComposer — compose and publish a post to a channel.
2
+ * PostComposer — compose, save, and publish a post to a channel.
3
3
  * @module comradecircle/components
4
4
  *
5
- * Writes `comCreatePost({ channelId, body, tags })`. Text + tags only (file
6
- * attachments are a future enhancement — the svc accepts attachmentFileIds but
7
- * file upload/preview is out of scope for v1).
5
+ * Writes `comCreatePost({ channelId, body, tags, attachmentFileIds, status })`.
6
+ *
7
+ * `ComCreatePostInput.status` (`ComPostStatus`, server default `PUBLISHED`) is
8
+ * what backs the "Save draft" action: a draft is a real `ComPost` row with
9
+ * `status: DRAFT`, invisible to the channel feed (which reads
10
+ * `comListPosts(status: PUBLISHED)`) until its author publishes it through
11
+ * `comUpdatePost(id, { status: PUBLISHED })` from {@link DraftsPanel}.
8
12
  */
9
13
  interface PostComposerProps {
10
14
  channelId: string;
11
15
  channelName?: string;
12
16
  onPosted?: () => void;
17
+ /** Fired after a draft is stored, so the host can reveal its drafts list. */
18
+ onDraftSaved?: () => void;
13
19
  /**
14
20
  * 'announcement' renders the manager-only broadcast composer with distinct
15
21
  * styling (accent border + "New announcement" affordance) per BOFF-3969's
@@ -17,6 +23,6 @@ interface PostComposerProps {
17
23
  */
18
24
  variant?: 'default' | 'announcement';
19
25
  }
20
- export declare function PostComposer({ channelId, channelName, onPosted, variant, }: PostComposerProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function PostComposer({ channelId, channelName, onPosted, onDraftSaved, variant, }: PostComposerProps): import("react/jsx-runtime").JSX.Element;
21
27
  export {};
22
28
  //# sourceMappingURL=PostComposer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostComposer.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/PostComposer.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,cAAc,CAAC;CACtC;AAED,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,WAAW,EACX,QAAQ,EACR,OAAmB,GACpB,EAAE,iBAAiB,2CAyGnB"}
1
+ {"version":3,"file":"PostComposer.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/PostComposer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,cAAc,CAAC;CACtC;AAED,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,OAAmB,GACpB,EAAE,iBAAiB,2CA+HnB"}
@@ -1,83 +1,95 @@
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 { useComCreatePost as n } from "../hooks/useComradeCircleApi.js";
3
+ import { useComCreatePost as t } from "../hooks/useComradeCircleApi.js";
5
4
  import "../hooks/index.js";
5
+ import { cn as n } from "../utils/cn.js";
6
6
  import { Avatar as r } from "./Avatar.js";
7
7
  import { describeError as i, notify as a } from "../lib/toast.js";
8
8
  import { AttachmentPicker as o } from "./ImageAttachments.js";
9
9
  import { TagInput as s } from "./TagInput.js";
10
10
  import { useState as c } from "react";
11
- import { Megaphone as l, Send as u } from "lucide-react";
12
- import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
13
- import { Button as m, GlassCard as h, Textarea as g } from "@burdenoff/fe-libs/ui";
11
+ import { FileText as l, Megaphone as u, Send as d } from "lucide-react";
12
+ import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
13
+ import { Button as h, GlassCard as g, Textarea as _ } from "@burdenoff/fe-libs/ui";
14
14
  //#region src/comradecircle/components/PostComposer.tsx
15
- function _({ channelId: _, channelName: v, onPosted: y, variant: b = "default" }) {
16
- let x = b === "announcement", { currentUser: S } = e(), C = n(), [w, T] = c(""), [E, D] = c([]), [O, k] = c(!1), [A, j] = c([]), M = async () => {
17
- let e = w.trim();
18
- if (!(!e && A.length === 0)) try {
19
- await C.mutateAsync({
20
- channelId: _,
21
- body: e,
22
- tags: E.length ? E : null,
23
- attachmentFileIds: A.length ? A : null
24
- }), T(""), D([]), j([]), k(!1), a.success(x ? "Announcement published" : "Posted", v ? `Shared to #${v}` : void 0), y?.();
15
+ function v({ channelId: v, channelName: y, onPosted: b, onDraftSaved: x, variant: S = "default" }) {
16
+ let C = S === "announcement", { currentUser: w } = e(), T = t(), [E, D] = c(""), [O, k] = c([]), [A, j] = c(!1), [M, N] = c([]), P = !E.trim() && M.length === 0, F = async (e) => {
17
+ let t = E.trim();
18
+ if (!t && M.length === 0) return;
19
+ let n = e === "DRAFT";
20
+ try {
21
+ await T.mutateAsync({
22
+ channelId: v,
23
+ body: t,
24
+ tags: O.length ? O : null,
25
+ attachmentFileIds: M.length ? M : null,
26
+ status: e
27
+ }), D(""), k([]), N([]), j(!1), n ? (a.success("Draft saved", y ? `Only you can see it until you publish it to #${y}.` : void 0), x?.()) : (a.success(C ? "Announcement published" : "Posted", y ? `Shared to #${y}` : void 0), b?.());
25
28
  } catch (e) {
26
- a.error("Could not publish post", i(e));
29
+ a.error(n ? "Could not save draft" : "Could not publish post", i(e));
27
30
  }
28
31
  };
29
- return /* @__PURE__ */ p(h, {
32
+ return /* @__PURE__ */ m(g, {
30
33
  treatment: "plain",
31
- className: t("p-4", x && "border-2 border-[color:var(--color-accent)]"),
32
- children: [x && /* @__PURE__ */ p("div", {
34
+ className: n("p-4", C && "border-2 border-[color:var(--color-accent)]"),
35
+ children: [C && /* @__PURE__ */ m("div", {
33
36
  className: "mb-3 flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-accent",
34
- children: [/* @__PURE__ */ f(l, { className: "h-3.5 w-3.5" }), " New announcement"]
35
- }), /* @__PURE__ */ p("div", {
37
+ children: [/* @__PURE__ */ p(u, { className: "h-3.5 w-3.5" }), " New announcement"]
38
+ }), /* @__PURE__ */ m("div", {
36
39
  className: "flex gap-3",
37
- children: [/* @__PURE__ */ f(r, {
38
- name: S?.name ?? S?.email ?? "You",
40
+ children: [/* @__PURE__ */ p(r, {
41
+ name: w?.name ?? w?.email ?? "You",
39
42
  size: "md"
40
- }), /* @__PURE__ */ p("div", {
43
+ }), /* @__PURE__ */ m("div", {
41
44
  className: "flex-1",
42
45
  children: [
43
- /* @__PURE__ */ f(g, {
44
- value: w,
45
- onChange: (e) => T(e.target.value),
46
- placeholder: x ? `Broadcast an announcement to #${v ?? "this channel"}…` : v ? `Share something in #${v}…` : "Share something…",
46
+ /* @__PURE__ */ p(_, {
47
+ value: E,
48
+ onChange: (e) => D(e.target.value),
49
+ placeholder: C ? `Broadcast an announcement to #${y ?? "this channel"}…` : y ? `Share something in #${y}…` : "Share something…",
47
50
  rows: 2,
48
51
  className: "resize-none border-0 bg-transparent px-0 shadow-none focus-visible:ring-0",
49
52
  onKeyDown: (e) => {
50
- e.key === "Enter" && (e.metaKey || e.ctrlKey) && (e.preventDefault(), M());
53
+ e.key === "Enter" && (e.metaKey || e.ctrlKey) && (e.preventDefault(), F("PUBLISHED"));
51
54
  }
52
55
  }),
53
- O && /* @__PURE__ */ f("div", {
56
+ A && /* @__PURE__ */ p("div", {
54
57
  className: "mt-2",
55
- children: /* @__PURE__ */ f(s, {
56
- value: E,
57
- onChange: D,
58
+ children: /* @__PURE__ */ p(s, {
59
+ value: O,
60
+ onChange: k,
58
61
  placeholder: "Add topics…"
59
62
  })
60
63
  }),
61
- /* @__PURE__ */ f("div", {
64
+ /* @__PURE__ */ p("div", {
62
65
  className: "mt-2",
63
- children: /* @__PURE__ */ f(o, {
64
- fileIds: A,
65
- onChange: j,
66
- disabled: C.isPending
66
+ children: /* @__PURE__ */ p(o, {
67
+ fileIds: M,
68
+ onChange: N,
69
+ disabled: T.isPending
67
70
  })
68
71
  }),
69
- /* @__PURE__ */ p("div", {
72
+ /* @__PURE__ */ m("div", {
70
73
  className: "mt-3 flex items-center justify-between",
71
- children: [/* @__PURE__ */ f("button", {
74
+ children: [/* @__PURE__ */ p("button", {
72
75
  type: "button",
73
- onClick: () => k((e) => !e),
76
+ onClick: () => j((e) => !e),
74
77
  className: "text-xs font-medium text-text-secondary transition-colors hover:text-text-primary",
75
- children: O ? "Hide topics" : "# Add topics"
76
- }), /* @__PURE__ */ f(m, {
77
- size: "sm",
78
- onClick: M,
79
- disabled: !w.trim() && A.length === 0 || C.isPending,
80
- children: x ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(l, { className: "mr-1.5 h-4 w-4" }), " Broadcast"] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(u, { className: "mr-1.5 h-4 w-4" }), " Post"] })
78
+ children: A ? "Hide topics" : "# Add topics"
79
+ }), /* @__PURE__ */ m("div", {
80
+ className: "flex items-center gap-2",
81
+ children: [/* @__PURE__ */ m(h, {
82
+ size: "sm",
83
+ variant: "outline",
84
+ onClick: () => void F("DRAFT"),
85
+ disabled: P || T.isPending,
86
+ children: [/* @__PURE__ */ p(l, { className: "mr-1.5 h-4 w-4" }), " Save draft"]
87
+ }), /* @__PURE__ */ p(h, {
88
+ size: "sm",
89
+ onClick: () => void F("PUBLISHED"),
90
+ disabled: P || T.isPending,
91
+ children: C ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(u, { className: "mr-1.5 h-4 w-4" }), " Broadcast"] }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(d, { className: "mr-1.5 h-4 w-4" }), " Post"] })
92
+ })]
81
93
  })]
82
94
  })
83
95
  ]
@@ -86,6 +98,6 @@ function _({ channelId: _, channelName: v, onPosted: y, variant: b = "default" }
86
98
  });
87
99
  }
88
100
  //#endregion
89
- export { _ as PostComposer };
101
+ export { v as PostComposer };
90
102
 
91
103
  //# sourceMappingURL=PostComposer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostComposer.js","names":[],"sources":["../../../src/comradecircle/components/PostComposer.tsx"],"sourcesContent":["/**\n * PostComposer — compose and publish a post to a channel.\n * @module comradecircle/components\n *\n * Writes `comCreatePost({ channelId, body, tags })`. Text + tags only (file\n * attachments are a future enhancement — the svc accepts attachmentFileIds but\n * file upload/preview is out of scope for v1).\n */\n\nimport { useState } from 'react';\nimport { Send, Megaphone } from 'lucide-react';\nimport { Button, Textarea, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { TagInput } from './TagInput';\nimport { Avatar } from './Avatar';\nimport { AttachmentPicker } from './ImageAttachments';\nimport { useComCreatePost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\n\ninterface PostComposerProps {\n channelId: string;\n channelName?: string;\n onPosted?: () => void;\n /**\n * 'announcement' renders the manager-only broadcast composer with distinct\n * styling (accent border + \"New announcement\" affordance) per BOFF-3969's\n * mock; 'default' is the regular post composer used on TEXT/FORUM channels.\n */\n variant?: 'default' | 'announcement';\n}\n\nexport function PostComposer({\n channelId,\n channelName,\n onPosted,\n variant = 'default',\n}: PostComposerProps) {\n const isAnnouncement = variant === 'announcement';\n const { currentUser } = useComradeCircle();\n const createPost = useComCreatePost();\n const [body, setBody] = useState('');\n const [tags, setTags] = useState<string[]>([]);\n const [showTags, setShowTags] = useState(false);\n const [attachmentFileIds, setAttachmentFileIds] = useState<string[]>([]);\n\n const submit = async () => {\n const text = body.trim();\n if (!text && attachmentFileIds.length === 0) return;\n try {\n await createPost.mutateAsync({\n channelId,\n body: text,\n tags: tags.length ? tags : null,\n attachmentFileIds: attachmentFileIds.length ? attachmentFileIds : null,\n });\n setBody('');\n setTags([]);\n setAttachmentFileIds([]);\n setShowTags(false);\n notify.success(\n isAnnouncement ? 'Announcement published' : 'Posted',\n channelName ? `Shared to #${channelName}` : undefined\n );\n onPosted?.();\n } catch (err) {\n notify.error('Could not publish post', describeError(err));\n }\n };\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn('p-4', isAnnouncement && 'border-2 border-[color:var(--color-accent)]')}\n >\n {isAnnouncement && (\n <div className=\"mb-3 flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-accent\">\n <Megaphone className=\"h-3.5 w-3.5\" /> New announcement\n </div>\n )}\n <div className=\"flex gap-3\">\n <Avatar name={currentUser?.name ?? currentUser?.email ?? 'You'} size=\"md\" />\n <div className=\"flex-1\">\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder={\n isAnnouncement\n ? `Broadcast an announcement to #${channelName ?? 'this channel'}…`\n : channelName\n ? `Share something in #${channelName}…`\n : 'Share something…'\n }\n rows={2}\n className=\"resize-none border-0 bg-transparent px-0 shadow-none focus-visible:ring-0\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void submit();\n }\n }}\n />\n {showTags && (\n <div className=\"mt-2\">\n <TagInput value={tags} onChange={setTags} placeholder=\"Add topics…\" />\n </div>\n )}\n <div className=\"mt-2\">\n <AttachmentPicker\n fileIds={attachmentFileIds}\n onChange={setAttachmentFileIds}\n disabled={createPost.isPending}\n />\n </div>\n <div className=\"mt-3 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => setShowTags((s) => !s)}\n className=\"text-xs font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n {showTags ? 'Hide topics' : '# Add topics'}\n </button>\n <Button\n size=\"sm\"\n onClick={submit}\n disabled={(!body.trim() && attachmentFileIds.length === 0) || createPost.isPending}\n >\n {isAnnouncement ? (\n <>\n <Megaphone className=\"mr-1.5 h-4 w-4\" /> Broadcast\n </>\n ) : (\n <>\n <Send className=\"mr-1.5 h-4 w-4\" /> Post\n </>\n )}\n </Button>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAgCA,SAAgB,EAAa,EAC3B,cACA,gBACA,aACA,aAAU,aACU;CACpB,IAAM,IAAiB,MAAY,gBAC7B,EAAE,mBAAgB,EAAiB,GACnC,IAAa,EAAiB,GAC9B,CAAC,GAAM,KAAW,EAAS,EAAE,GAC7B,CAAC,GAAM,KAAW,EAAmB,CAAC,CAAC,GACvC,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,CAAC,GAAmB,KAAwB,EAAmB,CAAC,CAAC,GAEjE,IAAS,YAAY;EACzB,IAAM,IAAO,EAAK,KAAK;EACnB,OAAC,KAAQ,EAAkB,WAAW,IAC1C,IAAI;GAeF,AAdA,MAAM,EAAW,YAAY;IAC3B;IACA,MAAM;IACN,MAAM,EAAK,SAAS,IAAO;IAC3B,mBAAmB,EAAkB,SAAS,IAAoB;GACpE,CAAC,GACD,EAAQ,EAAE,GACV,EAAQ,CAAC,CAAC,GACV,EAAqB,CAAC,CAAC,GACvB,EAAY,EAAK,GACjB,EAAO,QACL,IAAiB,2BAA2B,UAC5C,IAAc,cAAc,MAAgB,KAAA,CAC9C,GACA,IAAW;EACb,SAAS,GAAK;GACZ,EAAO,MAAM,0BAA0B,EAAc,CAAG,CAAC;EAC3D;CACF;CAEA,OACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,EAAG,OAAO,KAAkB,6CAA6C;YAFtF,CAIG,KACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAW,WAAU,cAAe,CAAA,GAAC,mBAClC;MAEP,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAQ,MAAM,GAAa,QAAQ,GAAa,SAAS;IAAO,MAAK;GAAM,CAAA,GAC3E,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAQ,EAAE,OAAO,KAAK;MACvC,aACE,IACI,iCAAiC,KAAe,eAAe,KAC/D,IACE,uBAAuB,EAAY,KACnC;MAER,MAAM;MACN,WAAU;MACV,YAAY,MAAM;OAChB,AAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,aACvC,EAAE,eAAe,GACjB,EAAY;MAEhB;KACD,CAAA;KACA,KACC,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAU,OAAO;OAAM,UAAU;OAAS,aAAY;MAAe,CAAA;KAClE,CAAA;KAEP,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OACE,SAAS;OACT,UAAU;OACV,UAAU,EAAW;MACtB,CAAA;KACE,CAAA;KACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAa,MAAM,CAAC,CAAC;OACpC,WAAU;iBAET,IAAW,gBAAgB;MACtB,CAAA,GACR,kBAAC,GAAD;OACE,MAAK;OACL,SAAS;OACT,UAAW,CAAC,EAAK,KAAK,KAAK,EAAkB,WAAW,KAAM,EAAW;iBAExE,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAW,WAAU,iBAAkB,CAAA,GAAC,YACxC,EAAA,CAAA,IAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,OACnC,EAAA,CAAA;MAEE,CAAA,CACL;;IACF;KACF;IACI;;AAEf"}
1
+ {"version":3,"file":"PostComposer.js","names":[],"sources":["../../../src/comradecircle/components/PostComposer.tsx"],"sourcesContent":["/**\n * PostComposer — compose, save, and publish a post to a channel.\n * @module comradecircle/components\n *\n * Writes `comCreatePost({ channelId, body, tags, attachmentFileIds, status })`.\n *\n * `ComCreatePostInput.status` (`ComPostStatus`, server default `PUBLISHED`) is\n * what backs the \"Save draft\" action: a draft is a real `ComPost` row with\n * `status: DRAFT`, invisible to the channel feed (which reads\n * `comListPosts(status: PUBLISHED)`) until its author publishes it through\n * `comUpdatePost(id, { status: PUBLISHED })` from {@link DraftsPanel}.\n */\n\nimport { useState } from 'react';\nimport { Send, Megaphone, FileText } from 'lucide-react';\nimport { Button, Textarea, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { TagInput } from './TagInput';\nimport { Avatar } from './Avatar';\nimport { AttachmentPicker } from './ImageAttachments';\nimport { useComCreatePost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPostStatus } from '../types';\n\ninterface PostComposerProps {\n channelId: string;\n channelName?: string;\n onPosted?: () => void;\n /** Fired after a draft is stored, so the host can reveal its drafts list. */\n onDraftSaved?: () => void;\n /**\n * 'announcement' renders the manager-only broadcast composer with distinct\n * styling (accent border + \"New announcement\" affordance) per BOFF-3969's\n * mock; 'default' is the regular post composer used on TEXT/FORUM channels.\n */\n variant?: 'default' | 'announcement';\n}\n\nexport function PostComposer({\n channelId,\n channelName,\n onPosted,\n onDraftSaved,\n variant = 'default',\n}: PostComposerProps) {\n const isAnnouncement = variant === 'announcement';\n const { currentUser } = useComradeCircle();\n const createPost = useComCreatePost();\n const [body, setBody] = useState('');\n const [tags, setTags] = useState<string[]>([]);\n const [showTags, setShowTags] = useState(false);\n const [attachmentFileIds, setAttachmentFileIds] = useState<string[]>([]);\n\n const isEmpty = !body.trim() && attachmentFileIds.length === 0;\n\n const submit = async (status: ComPostStatus) => {\n const text = body.trim();\n if (!text && attachmentFileIds.length === 0) return;\n const draft = status === 'DRAFT';\n try {\n await createPost.mutateAsync({\n channelId,\n body: text,\n tags: tags.length ? tags : null,\n attachmentFileIds: attachmentFileIds.length ? attachmentFileIds : null,\n status,\n });\n setBody('');\n setTags([]);\n setAttachmentFileIds([]);\n setShowTags(false);\n if (draft) {\n notify.success(\n 'Draft saved',\n channelName ? `Only you can see it until you publish it to #${channelName}.` : undefined\n );\n onDraftSaved?.();\n } else {\n notify.success(\n isAnnouncement ? 'Announcement published' : 'Posted',\n channelName ? `Shared to #${channelName}` : undefined\n );\n onPosted?.();\n }\n } catch (err) {\n notify.error(draft ? 'Could not save draft' : 'Could not publish post', describeError(err));\n }\n };\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn('p-4', isAnnouncement && 'border-2 border-[color:var(--color-accent)]')}\n >\n {isAnnouncement && (\n <div className=\"mb-3 flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-accent\">\n <Megaphone className=\"h-3.5 w-3.5\" /> New announcement\n </div>\n )}\n <div className=\"flex gap-3\">\n <Avatar name={currentUser?.name ?? currentUser?.email ?? 'You'} size=\"md\" />\n <div className=\"flex-1\">\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder={\n isAnnouncement\n ? `Broadcast an announcement to #${channelName ?? 'this channel'}…`\n : channelName\n ? `Share something in #${channelName}…`\n : 'Share something…'\n }\n rows={2}\n className=\"resize-none border-0 bg-transparent px-0 shadow-none focus-visible:ring-0\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void submit('PUBLISHED');\n }\n }}\n />\n {showTags && (\n <div className=\"mt-2\">\n <TagInput value={tags} onChange={setTags} placeholder=\"Add topics…\" />\n </div>\n )}\n <div className=\"mt-2\">\n <AttachmentPicker\n fileIds={attachmentFileIds}\n onChange={setAttachmentFileIds}\n disabled={createPost.isPending}\n />\n </div>\n <div className=\"mt-3 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => setShowTags((s) => !s)}\n className=\"text-xs font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n {showTags ? 'Hide topics' : '# Add topics'}\n </button>\n <div className=\"flex items-center gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void submit('DRAFT')}\n disabled={isEmpty || createPost.isPending}\n >\n <FileText className=\"mr-1.5 h-4 w-4\" /> Save draft\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void submit('PUBLISHED')}\n disabled={isEmpty || createPost.isPending}\n >\n {isAnnouncement ? (\n <>\n <Megaphone className=\"mr-1.5 h-4 w-4\" /> Broadcast\n </>\n ) : (\n <>\n <Send className=\"mr-1.5 h-4 w-4\" /> Post\n </>\n )}\n </Button>\n </div>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAuCA,SAAgB,EAAa,EAC3B,cACA,gBACA,aACA,iBACA,aAAU,aACU;CACpB,IAAM,IAAiB,MAAY,gBAC7B,EAAE,mBAAgB,EAAiB,GACnC,IAAa,EAAiB,GAC9B,CAAC,GAAM,KAAW,EAAS,EAAE,GAC7B,CAAC,GAAM,KAAW,EAAmB,CAAC,CAAC,GACvC,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,CAAC,GAAmB,KAAwB,EAAmB,CAAC,CAAC,GAEjE,IAAU,CAAC,EAAK,KAAK,KAAK,EAAkB,WAAW,GAEvD,IAAS,OAAO,MAA0B;EAC9C,IAAM,IAAO,EAAK,KAAK;EACvB,IAAI,CAAC,KAAQ,EAAkB,WAAW,GAAG;EAC7C,IAAM,IAAQ,MAAW;EACzB,IAAI;GAYF,AAXA,MAAM,EAAW,YAAY;IAC3B;IACA,MAAM;IACN,MAAM,EAAK,SAAS,IAAO;IAC3B,mBAAmB,EAAkB,SAAS,IAAoB;IAClE;GACF,CAAC,GACD,EAAQ,EAAE,GACV,EAAQ,CAAC,CAAC,GACV,EAAqB,CAAC,CAAC,GACvB,EAAY,EAAK,GACb,KACF,EAAO,QACL,eACA,IAAc,gDAAgD,EAAY,KAAK,KAAA,CACjF,GACA,IAAe,MAEf,EAAO,QACL,IAAiB,2BAA2B,UAC5C,IAAc,cAAc,MAAgB,KAAA,CAC9C,GACA,IAAW;EAEf,SAAS,GAAK;GACZ,EAAO,MAAM,IAAQ,yBAAyB,0BAA0B,EAAc,CAAG,CAAC;EAC5F;CACF;CAEA,OACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,EAAG,OAAO,KAAkB,6CAA6C;YAFtF,CAIG,KACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAW,WAAU,cAAe,CAAA,GAAC,mBAClC;MAEP,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAQ,MAAM,GAAa,QAAQ,GAAa,SAAS;IAAO,MAAK;GAAM,CAAA,GAC3E,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAQ,EAAE,OAAO,KAAK;MACvC,aACE,IACI,iCAAiC,KAAe,eAAe,KAC/D,IACE,uBAAuB,EAAY,KACnC;MAER,MAAM;MACN,WAAU;MACV,YAAY,MAAM;OAChB,AAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,aACvC,EAAE,eAAe,GACjB,EAAY,WAAW;MAE3B;KACD,CAAA;KACA,KACC,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAU,OAAO;OAAM,UAAU;OAAS,aAAY;MAAe,CAAA;KAClE,CAAA;KAEP,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OACE,SAAS;OACT,UAAU;OACV,UAAU,EAAW;MACtB,CAAA;KACE,CAAA;KACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAa,MAAM,CAAC,CAAC;OACpC,WAAU;iBAET,IAAW,gBAAgB;MACtB,CAAA,GACR,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,MAAK;QACL,SAAQ;QACR,eAAe,KAAK,EAAO,OAAO;QAClC,UAAU,KAAW,EAAW;kBAJlC,CAME,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA,GAAC,aACjC;WACR,kBAAC,GAAD;QACE,MAAK;QACL,eAAe,KAAK,EAAO,WAAW;QACtC,UAAU,KAAW,EAAW;kBAE/B,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAW,WAAU,iBAAkB,CAAA,GAAC,YACxC,EAAA,CAAA,IAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,OACnC,EAAA,CAAA;OAEE,CAAA,CACL;QACF;;IACF;KACF;IACI;;AAEf"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReactionBar.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,OAAO,KAAK,EAAE,OAAO,EAAmB,MAAM,UAAU,CAAC;AAWzD,UAAU,gBAAgB;IACxB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAsFrD"}
1
+ {"version":3,"file":"ReactionBar.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,OAAO,KAAK,EAAE,OAAO,EAAmB,MAAM,UAAU,CAAC;AA+BzD,UAAU,gBAAgB;IACxB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAqGrD"}