@burdenoff/microfe-comradecircle 2026.827.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 (74) 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.js +13 -13
  13. package/dist/comradecircle/components/EventCard.js +5 -5
  14. package/dist/comradecircle/components/FileImage.js +5 -5
  15. package/dist/comradecircle/components/ImageAttachments.js +21 -21
  16. package/dist/comradecircle/components/MemberIdentity.js +4 -4
  17. package/dist/comradecircle/components/MobileBottomNav.d.ts.map +1 -1
  18. package/dist/comradecircle/components/MobileBottomNav.js +37 -30
  19. package/dist/comradecircle/components/MobileBottomNav.js.map +1 -1
  20. package/dist/comradecircle/components/Pagination.d.ts.map +1 -1
  21. package/dist/comradecircle/components/Pagination.js +37 -28
  22. package/dist/comradecircle/components/Pagination.js.map +1 -1
  23. package/dist/comradecircle/components/PollCard.js +13 -13
  24. package/dist/comradecircle/components/PostCard.d.ts.map +1 -1
  25. package/dist/comradecircle/components/PostCard.js +102 -89
  26. package/dist/comradecircle/components/PostCard.js.map +1 -1
  27. package/dist/comradecircle/components/PostComposer.js +4 -4
  28. package/dist/comradecircle/components/ReactionBar.d.ts.map +1 -1
  29. package/dist/comradecircle/components/ReactionBar.js +65 -55
  30. package/dist/comradecircle/components/ReactionBar.js.map +1 -1
  31. package/dist/comradecircle/components/StateViews.d.ts.map +1 -1
  32. package/dist/comradecircle/components/StateViews.js +38 -30
  33. package/dist/comradecircle/components/StateViews.js.map +1 -1
  34. package/dist/comradecircle/components/UpcomingEvents.js +17 -17
  35. package/dist/comradecircle/hooks/index.d.ts +1 -0
  36. package/dist/comradecircle/hooks/index.d.ts.map +1 -1
  37. package/dist/comradecircle/hooks/index.js +1 -0
  38. package/dist/comradecircle/hooks/useTr.d.ts +18 -0
  39. package/dist/comradecircle/hooks/useTr.d.ts.map +1 -0
  40. package/dist/comradecircle/hooks/useTr.js +12 -0
  41. package/dist/comradecircle/hooks/useTr.js.map +1 -0
  42. package/dist/comradecircle/pages/FeedPage.d.ts.map +1 -1
  43. package/dist/comradecircle/pages/FeedPage.js +75 -62
  44. package/dist/comradecircle/pages/FeedPage.js.map +1 -1
  45. package/dist/comradecircle/pages/circles/CircleDetailPage.d.ts.map +1 -1
  46. package/dist/comradecircle/pages/circles/CircleDetailPage.js +439 -401
  47. package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
  48. package/dist/comradecircle/pages/circles/CirclesPage.d.ts.map +1 -1
  49. package/dist/comradecircle/pages/circles/CirclesPage.js +124 -117
  50. package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
  51. package/dist/comradecircle/pages/events/EventDetailPage.d.ts.map +1 -1
  52. package/dist/comradecircle/pages/events/EventDetailPage.js +182 -153
  53. package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
  54. package/dist/comradecircle/pages/events/EventsPage.d.ts.map +1 -1
  55. package/dist/comradecircle/pages/events/EventsPage.js +100 -91
  56. package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
  57. package/dist/comradecircle/pages/members/MemberProfilePage.d.ts.map +1 -1
  58. package/dist/comradecircle/pages/members/MemberProfilePage.js +172 -159
  59. package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
  60. package/dist/comradecircle/pages/members/MembersPage.d.ts.map +1 -1
  61. package/dist/comradecircle/pages/members/MembersPage.js +103 -104
  62. package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
  63. package/dist/comradecircle/pages/monetization/MonetizationPage.d.ts.map +1 -1
  64. package/dist/comradecircle/pages/monetization/MonetizationPage.js +319 -297
  65. package/dist/comradecircle/pages/monetization/MonetizationPage.js.map +1 -1
  66. package/dist/comradecircle/pages/posts/PostThreadPage.d.ts.map +1 -1
  67. package/dist/comradecircle/pages/posts/PostThreadPage.js +37 -32
  68. package/dist/comradecircle/pages/posts/PostThreadPage.js.map +1 -1
  69. package/dist/comradecircle/pages/profile/MyProfilePage.d.ts.map +1 -1
  70. package/dist/comradecircle/pages/profile/MyProfilePage.js +182 -177
  71. package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
  72. package/dist/comradecircle/utils/i18n.js +15 -0
  73. package/dist/comradecircle/utils/i18n.js.map +1 -0
  74. package/package.json +1 -1
@@ -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":";;;;;;;;;;;;;;;;AAyCA,SAAgB,EAAS,EAAE,SAAM,qBAAkB,IAAO,oBAAiB,MAAwB;CACjG,IAAM,EAAE,gBAAa,kBAAe,EAAiB,GAC/C,IAAa,EAAiB,GAC9B,IAAa,EAAiB,GAC9B,CAAC,GAAU,KAAe,EAAS,CAAe,GAClD,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAO,KAAY,EAAS,EAAK,IAAI,GAEtC,IAAW,GAAQ,GAAa,MAAM,EAAY,OAAO,EAAK;CAmCpE,OACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,EACT,8GACA,EAAK,YAAY,uBACnB;YALF;GAOE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,UAAU,EAAK;KACf,SAAQ;KACR,MAAK;KACL,QAAQ,EAAmB,EAAK,SAAS;KACzC,cAAA;IACD,CAAA,GACD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAK,YACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,GAAC,SACvB;SAEP,KACC,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAqB,SAAA;gBACnB,kBAAC,UAAD;OACE,MAAK;OACL,cAAW;OACX,WAAU;iBAEV,kBAAC,GAAD,EAAgB,WAAU,UAAW,CAAA;MAC/B,CAAA;KACW,CAAA,GACrB,kBAAC,GAAD;MAAqB,OAAM;gBAA3B;OACE,kBAAC,GAAD;QAAkB,eAAe,EAAW,EAAI;kBAAhD,CACE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA,GAAC,OACnB;;OAClB,kBAAC,GAAD;QAAkB,SAAS,YAvDX;SAC5B,IAAI;UAEF,AADA,MAAM,EAAW,YAAY;WAAE,IAAI,EAAK;WAAI,OAAO,EAAE,UAAU,CAAC,EAAK,SAAS;UAAE,CAAC,GACjF,EAAO,QAAQ,EAAK,WAAW,kBAAkB,aAAa;SAChE,SAAS,GAAK;UACZ,EAAO,MAAM,yBAAyB,EAAc,CAAG,CAAC;SAC1D;QACF;kBAgDc;SACE,kBAAC,GAAD,EAAK,WAAU,eAAgB,CAAA;SAAC;SAAE,EAAK,WAAW,UAAU;QAC5C;;OAClB,kBAAC,GAAD,CAAwB,CAAA;OACxB,kBAAC,GAAD;QACE,SAAS,YAnDE;SACzB,IAAI;UAEF,AADA,MAAM,EAAW,YAAY;WAAE,IAAI,EAAK;WAAI,WAAW,EAAK;UAAU,CAAC,GACvE,EAAO,QAAQ,cAAc;SAC/B,SAAS,GAAK;UACZ,EAAO,MAAM,yBAAyB,EAAc,CAAG,CAAC;SAC1D;QACF;QA6CgB,WAAU;kBAFZ,CAIE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA,GAAC,SACnB;;MACC;OACT,EAAA,CAAA,CAEb;MACF;;GAEJ,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAS,EAAE,OAAO,KAAK;KACxC,MAAM;KACN,WAAU;IACX,CAAA,GACD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,SAAQ;MACR,MAAK;MACL,eAAe;OAEb,AADA,EAAS,EAAK,IAAI,GAClB,EAAW,EAAK;MAClB;gBACD;KAEO,CAAA,GACR,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAS,YAzGR;OAC3B,IAAM,IAAO,EAAM,KAAK;OACxB,IAAI,CAAC,KAAQ,MAAS,EAAK,MAAM;QAC/B,EAAW,EAAK;QAChB;OACF;OACA,IAAI;QAGF,AAFA,MAAM,EAAW,YAAY;SAAE,IAAI,EAAK;SAAI,OAAO,EAAE,MAAM,EAAK;QAAE,CAAC,GACnE,EAAW,EAAK,GAChB,EAAO,QAAQ,cAAc;OAC/B,SAAS,GAAK;QACZ,EAAO,MAAM,yBAAyB,EAAc,CAAG,CAAC;OAC1D;MACF;MA4F+C,UAAU,EAAW;gBAAW;KAE7D,CAAA,CACL;MACF;QAEL,kBAAC,KAAD;IAAG,WAAU;cACV,EAAK;GACL,CAAA;GAGJ,EAAK,KAAK,SAAS,KAClB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,KAAK,MACd,kBAAC,QAAD;KAEE,WAAU;eAFZ,CAGC,KACG,CACE;OAJC,CAID,CACP;GACE,CAAA;GAGN,EAAK,kBAAkB,SAAS,KAAK,kBAAC,GAAD,EAAgB,SAAS,EAAK,kBAAoB,CAAA;GAExF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAmB,QAAO,CAAA,GAC1B,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,GAAa,MAAM,CAAC,CAAC;MACpC,iBAAe;MACf,WAAW,EACT,mGACA,IACI,oDACA,gEACN;gBATF;OAWE,kBAAC,GAAD,EAAe,WAAU,cAAe,CAAA;OACxC,kBAAC,QAAD;QAAM,WAAU;kBAAgB,EAAK;OAAmB,CAAA;OACxD,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAK,iBAAiB,IAAI,YAAY;OACnC,CAAA;MACA;SAEP,CAAC,KACA,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,SAAS,EAAK,IAAI;MAC5C,cAAW;MACX,OAAM;MACN,WAAU;gBALZ,CAOE,kBAAC,GAAD,EAAc,WAAU,cAAe,CAAA,GACvC,kBAAC,QAAD;OAAM,WAAU;iBAAmB;MAAiB,CAAA,CAC9C;OAEP;MACF;;GAEJ,KAAY,kBAAC,GAAD,EAAe,QAAQ,EAAK,GAAK,CAAA;EACrC;;AAEf"}
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":";;;;;;;;;;;;;;;;;AAyCA,SAAgB,EAAS,EAAE,SAAM,qBAAkB,IAAO,oBAAiB,MAAwB;CACjG,IAAM,EAAE,gBAAa,kBAAe,EAAiB,GAC/C,IAAa,EAAiB,GAC9B,IAAa,EAAiB,GAC9B,IAAK,EAAM,GACX,CAAC,GAAU,KAAe,EAAS,CAAe,GAClD,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAO,KAAY,EAAS,EAAK,IAAI,GAEtC,IAAW,GAAQ,GAAa,MAAM,EAAY,OAAO,EAAK;CAgDpE,OACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,EACT,8GACA,EAAK,YAAY,uBACnB;YALF;GAOE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,UAAU,EAAK;KACf,SAAQ;KACR,MAAK;KACL,QAAQ,EAAmB,EAAK,SAAS;KACzC,cAAA;IACD,CAAA,GACD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAK,YACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA;OAAC;OAAE,EAAG,4CAA4C,QAAQ;MAChF;SAEP,KACC,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAqB,SAAA;gBACnB,kBAAC,UAAD;OACE,MAAK;OACL,cAAY,EACV,0DACA,cACF;OACA,WAAU;iBAEV,kBAAC,GAAD,EAAgB,WAAU,UAAW,CAAA;MAC/B,CAAA;KACW,CAAA,GACrB,kBAAC,GAAD;MAAqB,OAAM;gBAA3B;OACE,kBAAC,GAAD;QAAkB,eAAe,EAAW,EAAI;kBAAhD;SACE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;SAAE;SACnC,EAAG,0CAA0C,MAAM;QACpC;;OAClB,kBAAC,GAAD;QAAkB,SAAS,YArEX;SAC5B,IAAI;UAEF,AADA,MAAM,EAAW,YAAY;WAAE,IAAI,EAAK;WAAI,OAAO,EAAE,UAAU,CAAC,EAAK,SAAS;UAAE,CAAC,GACjF,EAAO,QACL,EAAK,WACD,EAAG,qDAAqD,eAAe,IACvE,EAAG,mDAAmD,aAAa,CACzE;SACF,SAAS,GAAK;UACZ,EAAO,MACL,EAAG,8CAA8C,uBAAuB,GACxE,EAAc,CAAG,CACnB;SACF;QACF;kBAuDc;SACE,kBAAC,GAAD,EAAK,WAAU,eAAgB,CAAA;SAAE;SAChC,EAAK,WACF,EAAG,2CAA2C,OAAO,IACrD,EAAG,yCAAyC,KAAK;QACrC;;OAClB,kBAAC,GAAD,CAAwB,CAAA;OACxB,kBAAC,GAAD;QACE,SAAS,YA7DE;SACzB,IAAI;UAEF,AADA,MAAM,EAAW,YAAY;WAAE,IAAI,EAAK;WAAI,WAAW,EAAK;UAAU,CAAC,GACvE,EAAO,QAAQ,EAAG,oDAAoD,cAAc,CAAC;SACvF,SAAS,GAAK;UACZ,EAAO,MACL,EAAG,iDAAiD,uBAAuB,GAC3E,EAAc,CAAG,CACnB;SACF;QACF;QAoDgB,WAAU;kBAFZ;SAIE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;SAAE;SACnC,EAAG,4CAA4C,QAAQ;QACxC;;MACC;OACT,EAAA,CAAA,CAEb;MACF;;GAEJ,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAS,EAAE,OAAO,KAAK;KACxC,MAAM;KACN,WAAU;IACX,CAAA,GACD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,SAAQ;MACR,MAAK;MACL,eAAe;OAEb,AADA,EAAS,EAAK,IAAI,GAClB,EAAW,EAAK;MAClB;gBAEC,EAAG,4CAA4C,QAAQ;KAClD,CAAA,GACR,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAS,YA9HR;OAC3B,IAAM,IAAO,EAAM,KAAK;OACxB,IAAI,CAAC,KAAQ,MAAS,EAAK,MAAM;QAC/B,EAAW,EAAK;QAChB;OACF;OACA,IAAI;QAGF,AAFA,MAAM,EAAW,YAAY;SAAE,IAAI,EAAK;SAAI,OAAO,EAAE,MAAM,EAAK;QAAE,CAAC,GACnE,EAAW,EAAK,GAChB,EAAO,QAAQ,EAAG,oDAAoD,cAAc,CAAC;OACvF,SAAS,GAAK;QACZ,EAAO,MACL,EAAG,iDAAiD,uBAAuB,GAC3E,EAAc,CAAG,CACnB;OACF;MACF;MA8G+C,UAAU,EAAW;gBACvD,EAAG,0CAA0C,MAAM;KAC9C,CAAA,CACL;MACF;QAEL,kBAAC,KAAD;IAAG,WAAU;cACV,EAAK;GACL,CAAA;GAGJ,EAAK,KAAK,SAAS,KAClB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,KAAK,MACd,kBAAC,QAAD;KAEE,WAAU;eAFZ,CAGC,KACG,CACE;OAJC,CAID,CACP;GACE,CAAA;GAGN,EAAK,kBAAkB,SAAS,KAAK,kBAAC,GAAD,EAAgB,SAAS,EAAK,kBAAoB,CAAA;GAExF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAmB,QAAO,CAAA,GAC1B,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,GAAa,MAAM,CAAC,CAAC;MACpC,iBAAe;MACf,WAAW,EACT,mGACA,IACI,oDACA,gEACN;gBATF;OAWE,kBAAC,GAAD,EAAe,WAAU,cAAe,CAAA;OACxC,kBAAC,QAAD;QAAM,WAAU;kBAAgB,EAAK;OAAmB,CAAA;OACxD,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAK,iBAAiB,IACnB,EAAG,6CAA6C,SAAS,IACzD,EAAG,8CAA8C,UAAU;OAC3D,CAAA;MACA;SAEP,CAAC,KACA,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,SAAS,EAAK,IAAI;MAC5C,cAAY,EACV,yDACA,aACF;MACA,OAAO,EAAG,qDAAqD,aAAa;MAC5E,WAAU;gBARZ,CAUE,kBAAC,GAAD,EAAc,WAAU,cAAe,CAAA,GACvC,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,gDAAgD,aAAa;MAC7D,CAAA,CACA;OAEP;MACF;;GAEJ,KAAY,kBAAC,GAAD,EAAe,QAAQ,EAAK,GAAK,CAAA;EACrC;;AAEf"}
@@ -1,8 +1,8 @@
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";
@@ -13,7 +13,7 @@ import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
13
13
  import { Button as h, GlassCard as g, Textarea as _ } from "@burdenoff/fe-libs/ui";
14
14
  //#region src/comradecircle/components/PostComposer.tsx
15
15
  function v({ channelId: v, channelName: y, onPosted: b, onDraftSaved: x, variant: S = "default" }) {
16
- let C = S === "announcement", { currentUser: w } = e(), T = n(), [E, D] = c(""), [O, k] = c([]), [A, j] = c(!1), [M, N] = c([]), P = !E.trim() && M.length === 0, F = async (e) => {
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
17
  let t = E.trim();
18
18
  if (!t && M.length === 0) return;
19
19
  let n = e === "DRAFT";
@@ -31,7 +31,7 @@ function v({ channelId: v, channelName: y, onPosted: b, onDraftSaved: x, variant
31
31
  };
32
32
  return /* @__PURE__ */ m(g, {
33
33
  treatment: "plain",
34
- className: t("p-4", C && "border-2 border-[color:var(--color-accent)]"),
34
+ className: n("p-4", C && "border-2 border-[color:var(--color-accent)]"),
35
35
  children: [C && /* @__PURE__ */ m("div", {
36
36
  className: "mb-3 flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-accent",
37
37
  children: [/* @__PURE__ */ p(u, { className: "h-3.5 w-3.5" }), " New announcement"]
@@ -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"}
@@ -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"}
@@ -1,8 +1,8 @@
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 { useComEvents as n, useComRsvpEvent as r } from "../hooks/useComradeCircleApi.js";
3
+ import { useComEvents as t, useComRsvpEvent as n } from "../hooks/useComradeCircleApi.js";
5
4
  import "../hooks/index.js";
5
+ import { cn as r } from "../utils/cn.js";
6
6
  import { describeError as i, notify as a } from "../lib/toast.js";
7
7
  import { formatDateTime as o, formatNumber as s } from "../utils/formatters.js";
8
8
  import { useEffect as c, useMemo as l, useState as u } from "react";
@@ -18,17 +18,17 @@ function C(e = S) {
18
18
  return () => clearInterval(t);
19
19
  }, [e]), t;
20
20
  }
21
- function w({ limit: r = 3, className: i }) {
22
- let { navigateTo: a } = e(), o = C(), { data: s, isLoading: c, isError: u } = n({
21
+ function w({ limit: n = 3, className: i }) {
22
+ let { navigateTo: a } = e(), o = C(), { data: s, isLoading: c, isError: u } = t({
23
23
  status: "SCHEDULED",
24
24
  pagination: { limit: x }
25
- }), p = l(() => (s?.items ?? []).filter((e) => new Date(e.startsAt).getTime() >= o).slice(0, r), [
25
+ }), p = l(() => (s?.items ?? []).filter((e) => new Date(e.startsAt).getTime() >= o).slice(0, n), [
26
26
  s,
27
27
  o,
28
- r
28
+ n
29
29
  ]);
30
30
  return c || u || p.length === 0 ? null : /* @__PURE__ */ y("section", {
31
- className: t("mx-auto w-full max-w-2xl", i),
31
+ className: r("mx-auto w-full max-w-2xl", i),
32
32
  "aria-labelledby": "cc-upcoming",
33
33
  children: [/* @__PURE__ */ y("div", {
34
34
  className: "mb-2 flex items-center justify-between gap-3",
@@ -51,13 +51,13 @@ function w({ limit: r = 3, className: i }) {
51
51
  })]
52
52
  });
53
53
  }
54
- function T({ event: n }) {
55
- let { navigateTo: c } = e(), l = r(), [d, x] = u(null), S = d === "GOING", C = n.mode === "IN_PERSON" ? m : g, w = n.mode === "IN_PERSON" ? n.location || "In person" : n.mode === "HYBRID" ? `Hybrid${n.location ? ` · ${n.location}` : ""}` : "Online", T = async () => {
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
56
  try {
57
57
  await l.mutateAsync({
58
- eventId: n.id,
58
+ eventId: t.id,
59
59
  status: "GOING"
60
- }), x("GOING"), a.success("RSVP: Going", n.title);
60
+ }), x("GOING"), a.success("RSVP: Going", t.title);
61
61
  } catch (e) {
62
62
  a.error("Could not RSVP", i(e));
63
63
  }
@@ -74,13 +74,13 @@ function T({ event: n }) {
74
74
  className: "min-w-0 flex-1",
75
75
  children: [/* @__PURE__ */ v("button", {
76
76
  type: "button",
77
- onClick: () => c(`events/${n.id}`),
77
+ onClick: () => c(`events/${t.id}`),
78
78
  className: "block max-w-full truncate text-left text-sm font-semibold text-text-primary transition-colors hover:text-accent",
79
- children: n.title
79
+ children: t.title
80
80
  }), /* @__PURE__ */ y("div", {
81
81
  className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-text-tertiary",
82
82
  children: [
83
- /* @__PURE__ */ v("span", { children: o(n.startsAt) }),
83
+ /* @__PURE__ */ v("span", { children: o(t.startsAt) }),
84
84
  /* @__PURE__ */ v("span", {
85
85
  "aria-hidden": "true",
86
86
  children: "·"
@@ -101,8 +101,8 @@ function T({ event: n }) {
101
101
  className: "inline-flex items-center gap-1",
102
102
  children: [
103
103
  /* @__PURE__ */ v(h, { className: "h-3 w-3" }),
104
- s(n.rsvpCount),
105
- n.capacity ? ` / ${s(n.capacity)}` : ""
104
+ s(t.rsvpCount),
105
+ t.capacity ? ` / ${s(t.capacity)}` : ""
106
106
  ]
107
107
  })
108
108
  ]
@@ -114,7 +114,7 @@ function T({ event: n }) {
114
114
  onClick: () => void T(),
115
115
  disabled: l.isPending,
116
116
  "aria-pressed": S,
117
- className: t("shrink-0", S && "text-accent"),
117
+ className: r("shrink-0", S && "text-accent"),
118
118
  children: S ? /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(p, { className: "mr-1 h-3.5 w-3.5" }), " Going"] }) : "RSVP"
119
119
  })
120
120
  ]
@@ -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"}
@@ -1 +1 @@
1
- {"version":3,"file":"FeedPage.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/pages/FeedPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAkEH,wBAAgB,QAAQ,4CAmEvB"}
1
+ {"version":3,"file":"FeedPage.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/pages/FeedPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAkEH,wBAAgB,QAAQ,4CAgFvB"}