@burdenoff/microfe-comradecircle 2026.720.2 → 2026.720.3

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 (39) hide show
  1. package/dist/comradecircle/ComradeCircleRoutes.d.ts.map +1 -1
  2. package/dist/comradecircle/ComradeCircleRoutes.js +39 -34
  3. package/dist/comradecircle/ComradeCircleRoutes.js.map +1 -1
  4. package/dist/comradecircle/components/CommentThread.js +1 -1
  5. package/dist/comradecircle/components/MemberIdentity.js +1 -1
  6. package/dist/comradecircle/components/MobileBottomNav.d.ts +4 -0
  7. package/dist/comradecircle/components/MobileBottomNav.d.ts.map +1 -0
  8. package/dist/comradecircle/components/MobileBottomNav.js +65 -0
  9. package/dist/comradecircle/components/MobileBottomNav.js.map +1 -0
  10. package/dist/comradecircle/components/PostCard.d.ts +3 -1
  11. package/dist/comradecircle/components/PostCard.d.ts.map +1 -1
  12. package/dist/comradecircle/components/PostCard.js +86 -73
  13. package/dist/comradecircle/components/PostCard.js.map +1 -1
  14. package/dist/comradecircle/components/PostComposer.d.ts +7 -1
  15. package/dist/comradecircle/components/PostComposer.d.ts.map +1 -1
  16. package/dist/comradecircle/components/PostComposer.js +53 -49
  17. package/dist/comradecircle/components/PostComposer.js.map +1 -1
  18. package/dist/comradecircle/components/ReactionBar.js +1 -1
  19. package/dist/comradecircle/components/index.d.ts +1 -0
  20. package/dist/comradecircle/components/index.d.ts.map +1 -1
  21. package/dist/comradecircle/hooks/useComradeCircleApi.js +37 -22
  22. package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
  23. package/dist/comradecircle/index.js +1 -1
  24. package/dist/comradecircle/pages/FeedPage.js +1 -1
  25. package/dist/comradecircle/pages/circles/CircleDetailPage.js +242 -219
  26. package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
  27. package/dist/comradecircle/pages/circles/CirclesPage.d.ts.map +1 -1
  28. package/dist/comradecircle/pages/circles/CirclesPage.js +72 -39
  29. package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
  30. package/dist/comradecircle/pages/events/EventDetailPage.js +1 -1
  31. package/dist/comradecircle/pages/events/EventsPage.js +1 -1
  32. package/dist/comradecircle/pages/members/MemberProfilePage.js +1 -1
  33. package/dist/comradecircle/pages/members/MembersPage.js +1 -1
  34. package/dist/comradecircle/pages/posts/PostThreadPage.d.ts +14 -0
  35. package/dist/comradecircle/pages/posts/PostThreadPage.d.ts.map +1 -0
  36. package/dist/comradecircle/pages/posts/PostThreadPage.js +48 -0
  37. package/dist/comradecircle/pages/posts/PostThreadPage.js.map +1 -0
  38. package/dist/comradecircle/pages/profile/MyProfilePage.js +1 -1
  39. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CircleDetailPage.js","names":[],"sources":["../../../../src/comradecircle/pages/circles/CircleDetailPage.tsx"],"sourcesContent":["/**\n * CircleDetailPage — a single circle: channels & posts, members, events, polls.\n * @module comradecircle/pages/circles\n *\n * Reads `comCircle` (channels + memberships), `comListPosts`, `comListEvents`,\n * `comListPolls`. Writes join/leave, create channel, create poll, set member\n * role (via the shared cards/dialogs). The channel post feed is the heart of the\n * page; roles gate management actions (OWNER/ADMIN manage; members post).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Users,\n Hash,\n CalendarDays,\n BarChart3,\n Info,\n Plus,\n Megaphone,\n MessageCircle,\n Mic,\n MessagesSquare,\n Search,\n} from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Switch,\n GradientText,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../../utils/cn';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Tabs, type TabItem } from '../../components/Tabs';\nimport { MemberIdentity } from '../../components/MemberIdentity';\nimport { PostCard } from '../../components/PostCard';\nimport { PostComposer } from '../../components/PostComposer';\nimport { EventCard } from '../../components/EventCard';\nimport { PollCard } from '../../components/PollCard';\nimport { CreateEventDialog } from '../../components/CreateEventDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useComCircle,\n useComPosts,\n useComEvents,\n useComPolls,\n useComJoinCircle,\n useComLeaveCircle,\n useComCreateChannel,\n useComCreatePoll,\n useComSetMemberRole,\n useCursorPager,\n} from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatDate, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport { slugify } from '../../utils/slug';\nimport { notify, describeError } from '../../lib/toast';\nimport type {\n ComChannel,\n ComChannelType,\n ComCircle,\n ComMembership,\n ComMemberRole,\n} from '../../types';\n\ntype TabKey = 'channels' | 'members' | 'events' | 'polls' | 'about';\n\nconst CHANNEL_ICON: Record<ComChannelType, typeof Hash> = {\n TEXT: Hash,\n FORUM: MessagesSquare,\n ANNOUNCEMENT: Megaphone,\n VOICE: Mic,\n};\n\nconst MANAGER_ROLES: ComMemberRole[] = ['OWNER', 'ADMIN'];\nconst ROLE_OPTIONS: ComMemberRole[] = ['OWNER', 'ADMIN', 'MODERATOR', 'MEMBER'];\nconst POST_PAGE = 15;\n\nexport function CircleDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { currentUser, navigateTo } = useComradeCircle();\n const { data: circle, isLoading, isError, refetch } = useComCircle(id);\n const join = useComJoinCircle();\n const leave = useComLeaveCircle();\n const [tab, setTab] = useState<TabKey>('channels');\n\n const myMembership = useMemo<ComMembership | undefined>(\n () => circle?.memberships?.find((m) => m.memberId === currentUser?.id && m.status === 'ACTIVE'),\n [circle, currentUser]\n );\n const isMember = Boolean(myMembership);\n const canManage = myMembership ? MANAGER_ROLES.includes(myMembership.role) : false;\n\n if (isLoading) {\n return (\n <PageLayout title=\"Circle\">\n <LoadingView label=\"Loading circle…\" />\n </PageLayout>\n );\n }\n if (isError || !circle) {\n return (\n <PageLayout title=\"Circle\">\n <ErrorView\n title=\"Circle not found\"\n message=\"This circle may have been archived or you may not have access.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n const activeMembers = circle.memberships?.filter((m) => m.status === 'ACTIVE') ?? [];\n const tabs: TabItem<TabKey>[] = [\n {\n value: 'channels',\n label: 'Channels',\n icon: <Hash className=\"h-4 w-4\" />,\n count: circle.channels?.length ?? 0,\n },\n {\n value: 'members',\n label: 'Members',\n icon: <Users className=\"h-4 w-4\" />,\n count: activeMembers.length,\n },\n { value: 'events', label: 'Events', icon: <CalendarDays className=\"h-4 w-4\" /> },\n { value: 'polls', label: 'Polls', icon: <BarChart3 className=\"h-4 w-4\" /> },\n { value: 'about', label: 'About', icon: <Info className=\"h-4 w-4\" /> },\n ];\n\n return (\n <PageLayout\n title={\n <span className=\"flex items-center gap-3\">\n <Avatar name={circle.name} size=\"lg\" fileId={circle.avatar?.id} seed={circle.id} ring />\n <GradientText as=\"span\" className=\"text-2xl font-semibold tracking-tight\">\n {circle.name}\n </GradientText>\n </span>\n }\n description={circle.topic ?? undefined}\n aurora\n actions={\n <div className=\"flex items-center gap-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('circles')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n {isMember ? (\n <Button\n variant=\"outline\"\n onClick={() =>\n leave\n .mutateAsync(circle.id)\n .then(() => notify.success('Left circle'))\n .catch((e) => notify.error('Could not leave', describeError(e)))\n }\n disabled={leave.isPending}\n >\n Leave\n </Button>\n ) : circle.status === 'ACTIVE' ? (\n <Button\n onClick={() =>\n join\n .mutateAsync(circle.id)\n .then(() => notify.success('Joined circle'))\n .catch((e) => notify.error('Could not join', describeError(e)))\n }\n disabled={join.isPending}\n >\n Join circle\n </Button>\n ) : null}\n </div>\n }\n >\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n <StatusBadge status={circle.visibility} />\n {circle.status !== 'ACTIVE' && <StatusBadge status={circle.status} />}\n {myMembership && <StatusBadge status={myMembership.role} />}\n <span className=\"inline-flex items-center gap-1 text-sm text-text-tertiary\">\n <Users className=\"h-4 w-4\" /> {formatNumber(circle.memberCount)} members\n </span>\n </div>\n\n <Tabs items={tabs} value={tab} onChange={setTab} className=\"mb-5\" />\n\n {tab === 'channels' && (\n <ChannelsTab circle={circle} isMember={isMember} canManage={canManage} />\n )}\n {tab === 'members' && <MembersTab circle={circle} canManage={canManage} />}\n {tab === 'events' && <EventsTab circle={circle} isMember={isMember} />}\n {tab === 'polls' && <PollsTab circle={circle} isMember={isMember} canManage={canManage} />}\n {tab === 'about' && <AboutTab circle={circle} />}\n </PageLayout>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Channels tab — channel rail + selected channel post feed\n// ---------------------------------------------------------------------------\n\nfunction ChannelsTab({\n circle,\n isMember,\n canManage,\n}: {\n circle: ComCircle;\n isMember: boolean;\n canManage: boolean;\n}) {\n const channels = useMemo(\n () => [...(circle.channels ?? [])].sort((a, b) => a.position - b.position),\n [circle.channels]\n );\n const [selectedId, setSelectedId] = useState<string | undefined>(channels[0]?.id);\n const [createOpen, setCreateOpen] = useState(false);\n const selected = channels.find((c) => c.id === selectedId) ?? channels[0];\n\n if (channels.length === 0) {\n return (\n <>\n <EmptyState\n icon={<Hash className=\"h-10 w-10\" />}\n title=\"No channels yet\"\n description={\n canManage\n ? 'Create the first channel to start conversations in this circle.'\n : 'This circle has no channels yet. Check back soon.'\n }\n action={\n canManage ? (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New channel\n </Button>\n ) : undefined\n }\n />\n <CreateChannelDialog\n circleId={circle.id}\n open={createOpen}\n onOpenChange={setCreateOpen}\n onCreated={(ch) => setSelectedId(ch.id)}\n />\n </>\n );\n }\n\n return (\n <div className=\"grid gap-5 lg:grid-cols-[220px_1fr]\">\n {/* Channel rail (desktop) */}\n <aside className=\"hidden lg:block\">\n <div className=\"mb-2 flex items-center justify-between\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Channels\n </h3>\n {canManage && (\n <button\n type=\"button\"\n onClick={() => setCreateOpen(true)}\n aria-label=\"New channel\"\n className=\"rounded-md p-1 text-text-tertiary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <Plus className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n <nav className=\"flex flex-col gap-0.5\">\n {channels.map((ch) => {\n const Icon = CHANNEL_ICON[ch.type];\n const active = ch.id === selected?.id;\n return (\n <button\n key={ch.id}\n type=\"button\"\n onClick={() => setSelectedId(ch.id)}\n aria-current={active ? 'true' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-lg border-l-2 px-2.5 py-1.5 text-left text-sm transition-all',\n active\n ? 'border-[color:var(--color-accent)] bg-[var(--color-accent-soft)] font-medium text-text-primary shadow-sm'\n : 'border-transparent text-text-secondary hover:bg-bg-sunken hover:text-text-primary motion-safe:hover:translate-x-0.5'\n )}\n >\n <Icon\n className={cn('h-4 w-4 shrink-0', active ? 'text-accent' : 'text-text-tertiary')}\n />\n <span className=\"truncate\">{ch.name}</span>\n </button>\n );\n })}\n </nav>\n </aside>\n\n {/* Channel picker (mobile) */}\n <div className=\"lg:hidden\">\n <div className=\"flex items-center gap-2\">\n <Select value={selected?.id} onValueChange={setSelectedId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select a channel\" />\n </SelectTrigger>\n <SelectContent>\n {channels.map((ch) => (\n <SelectItem key={ch.id} value={ch.id}>\n #{ch.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {canManage && (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => setCreateOpen(true)}\n aria-label=\"New channel\"\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n </div>\n\n {/* Selected channel feed */}\n <div className=\"min-w-0\">\n {selected && <ChannelFeed channel={selected} canPost={isMember} canManage={canManage} />}\n </div>\n\n <CreateChannelDialog\n circleId={circle.id}\n open={createOpen}\n onOpenChange={setCreateOpen}\n onCreated={(ch) => setSelectedId(ch.id)}\n />\n </div>\n );\n}\n\nfunction ChannelFeed({\n channel,\n canPost,\n canManage,\n}: {\n channel: ComChannel;\n canPost: boolean;\n canManage: boolean;\n}) {\n const pager = useCursorPager();\n const { data, isLoading, isError, refetch } = useComPosts({\n channelId: channel.id,\n status: 'PUBLISHED',\n pagination: { limit: POST_PAGE, cursor: pager.cursor },\n });\n const posts = data?.items ?? [];\n // Voice channels never take text posts; announcements are manager-only; every\n // other channel type is open to any member.\n const canWrite =\n channel.type === 'VOICE' ? false : channel.type === 'ANNOUNCEMENT' ? canManage : canPost;\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-center gap-3\">\n {(() => {\n const Icon = CHANNEL_ICON[channel.type];\n return (\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-accent\">\n <Icon className=\"h-5 w-5\" />\n </span>\n );\n })()}\n <div className=\"min-w-0\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{channel.name}</h2>\n {channel.description && (\n <p className=\"text-sm text-text-secondary\">{channel.description}</p>\n )}\n </div>\n </div>\n\n {canWrite && (\n <PostComposer\n channelId={channel.id}\n channelName={channel.name}\n onPosted={() => pager.reset()}\n />\n )}\n\n {channel.type === 'ANNOUNCEMENT' && !canWrite && (\n <div className=\"rounded-xl border border-dashed border-border-seam p-6 text-center text-sm text-text-secondary\">\n <Megaphone className=\"mx-auto mb-2 h-6 w-6 text-text-tertiary\" />\n Only circle managers can post in announcement channels.\n </div>\n )}\n\n {channel.type === 'VOICE' && (\n <div className=\"rounded-xl border border-dashed border-border-seam p-6 text-center text-sm text-text-secondary\">\n <Mic className=\"mx-auto mb-2 h-6 w-6 text-text-tertiary\" />\n Voice channels are for live audio. Text posting is disabled here.\n </div>\n )}\n\n {isLoading ? (\n <LoadingView label=\"Loading posts…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load posts.\" onRetry={() => refetch()} />\n ) : posts.length === 0 ? (\n <EmptyState\n icon={<MessageCircle className=\"h-9 w-9\" />}\n title=\"No posts yet\"\n description={\n canWrite ? 'Be the first to post in this channel.' : 'Nothing has been posted here yet.'\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {posts.map((post) => (\n <PostCard key={post.id} post={post} />\n ))}\n <Pagination\n page={pager.page}\n pageSize={POST_PAGE}\n itemCount={posts.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Members tab\n// ---------------------------------------------------------------------------\n\nfunction MembersTab({ circle, canManage }: { circle: ComCircle; canManage: boolean }) {\n const setRole = useComSetMemberRole();\n const [query, setQuery] = useState('');\n const members = useMemo(\n () =>\n [...(circle.memberships ?? [])].sort(\n (a, b) => ROLE_OPTIONS.indexOf(a.role) - ROLE_OPTIONS.indexOf(b.role)\n ),\n [circle.memberships]\n );\n\n // Names resolve asynchronously inside MemberIdentity, so the filter works over\n // the data we hold here directly — role + membership status.\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return members;\n return members.filter(\n (m) =>\n formatEnumLabel(m.role).toLowerCase().includes(q) ||\n m.role.toLowerCase().includes(q) ||\n formatEnumLabel(m.status).toLowerCase().includes(q) ||\n m.status.toLowerCase().includes(q)\n );\n }, [members, query]);\n\n if (members.length === 0) {\n return <EmptyState icon={<Users className=\"h-9 w-9\" />} title=\"No members yet\" />;\n }\n\n const changeRole = async (membershipId: string, role: ComMemberRole) => {\n try {\n await setRole.mutateAsync({ membershipId, role, circleId: circle.id });\n notify.success('Role updated');\n } catch (err) {\n notify.error('Could not update role', describeError(err));\n }\n };\n\n const gridCols = canManage\n ? 'lg:grid-cols-[minmax(0,1fr)_120px_120px_190px]'\n : 'lg:grid-cols-[minmax(0,1fr)_120px_120px]';\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative w-full sm:max-w-xs\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Filter by role or status…\"\n className=\"pl-9\"\n aria-label=\"Filter members by role or status\"\n />\n </div>\n <span className=\"text-sm text-text-tertiary\">\n {filtered.length === members.length\n ? `${formatNumber(members.length)} member${members.length === 1 ? '' : 's'}`\n : `${formatNumber(filtered.length)} of ${formatNumber(members.length)}`}\n </span>\n </div>\n\n <div className=\"overflow-hidden rounded-2xl border border-border-seam bg-bg-surface\">\n <div\n className={cn(\n 'hidden border-b border-border-seam px-5 py-3 text-xs font-semibold uppercase tracking-wide text-text-tertiary lg:grid lg:items-center lg:gap-4',\n gridCols\n )}\n >\n <span>Member</span>\n <span>Status</span>\n <span>Role</span>\n {canManage && <span>Manage role</span>}\n </div>\n\n {filtered.length === 0 ? (\n <div className=\"px-5 py-10 text-center text-sm text-text-secondary\">\n No members match “{query.trim()}”.\n </div>\n ) : (\n filtered.map((m) => (\n <div\n key={m.id}\n className={cn(\n 'flex flex-col gap-3 border-b border-border-seam px-5 py-4 last:border-b-0 motion-safe:transition-colors hover:bg-bg-sunken/60 lg:grid lg:items-center lg:gap-4',\n gridCols\n )}\n >\n <MemberIdentity memberId={m.memberId} variant=\"row\" size=\"md\" showPresence />\n <div className=\"flex flex-wrap items-center gap-2 lg:contents\">\n <div>\n <StatusBadge status={m.status} />\n </div>\n <div>\n <StatusBadge status={m.role} />\n </div>\n {canManage && (\n <Select\n value={m.role}\n onValueChange={(v) => changeRole(m.id, v as ComMemberRole)}\n >\n <SelectTrigger className=\"h-9 w-40 lg:w-full\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ROLE_OPTIONS.map((r) => (\n <SelectItem key={r} value={r}>\n {formatEnumLabel(r)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n </div>\n ))\n )}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Events tab\n// ---------------------------------------------------------------------------\n\nfunction EventsTab({ circle, isMember }: { circle: ComCircle; isMember: boolean }) {\n const { navigateTo } = useComradeCircle();\n const { data, isLoading, isError, refetch } = useComEvents({ circleId: circle.id });\n const [createOpen, setCreateOpen] = useState(false);\n const events = data?.items ?? [];\n\n return (\n <div>\n <div className=\"mb-4 flex items-center justify-end\">\n {isMember && (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n )}\n </div>\n {isLoading ? (\n <LoadingView label=\"Loading events…\" />\n ) : isError ? (\n <ErrorView onRetry={() => refetch()} />\n ) : events.length === 0 ? (\n <EmptyState\n icon={<CalendarDays className=\"h-9 w-9\" />}\n title=\"No events scheduled\"\n description={\n isMember ? 'Schedule the first event for this circle.' : 'No upcoming events.'\n }\n action={\n isMember ? (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid gap-4 md:grid-cols-2\">\n {events.map((ev) => (\n <EventCard key={ev.id} event={ev} onOpen={() => navigateTo(`events/${ev.id}`)} />\n ))}\n </div>\n )}\n <CreateEventDialog circleId={circle.id} open={createOpen} onOpenChange={setCreateOpen} />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Polls tab\n// ---------------------------------------------------------------------------\n\nfunction PollsTab({\n circle,\n isMember,\n canManage,\n}: {\n circle: ComCircle;\n isMember: boolean;\n canManage: boolean;\n}) {\n const { data: polls, isLoading, isError, refetch } = useComPolls(circle.id);\n const [createOpen, setCreateOpen] = useState(false);\n\n return (\n <div>\n <div className=\"mb-4 flex items-center justify-end\">\n {isMember && (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New poll\n </Button>\n )}\n </div>\n {isLoading ? (\n <LoadingView label=\"Loading polls…\" />\n ) : isError ? (\n <ErrorView onRetry={() => refetch()} />\n ) : (polls ?? []).length === 0 ? (\n <EmptyState\n icon={<BarChart3 className=\"h-9 w-9\" />}\n title=\"No polls yet\"\n description={\n isMember ? 'Ask the circle a question with a poll.' : 'No polls have been created.'\n }\n action={\n isMember ? (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New poll\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-4\">\n {(polls ?? []).map((poll) => (\n <PollCard key={poll.id} poll={poll} canManage={canManage} />\n ))}\n </div>\n )}\n <CreatePollDialog circleId={circle.id} open={createOpen} onOpenChange={setCreateOpen} />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// About tab\n// ---------------------------------------------------------------------------\n\nfunction AboutTab({ circle }: { circle: ComCircle }) {\n return (\n <div className=\"mx-auto max-w-2xl\">\n <div className=\"rounded-2xl border border-border-seam bg-bg-surface p-6\">\n <h2 className=\"text-lg font-semibold text-text-primary\">About this circle</h2>\n <p className=\"mt-2 whitespace-pre-wrap text-sm text-text-secondary\">\n {circle.description || 'No description provided.'}\n </p>\n {circle.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {circle.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n <dl className=\"mt-6 grid grid-cols-2 gap-4 text-sm\">\n <div>\n <dt className=\"text-text-tertiary\">Visibility</dt>\n <dd className=\"mt-1 text-text-primary\">{formatEnumLabel(circle.visibility)}</dd>\n </div>\n <div>\n <dt className=\"text-text-tertiary\">Status</dt>\n <dd className=\"mt-1 text-text-primary\">{formatEnumLabel(circle.status)}</dd>\n </div>\n <div>\n <dt className=\"text-text-tertiary\">Members</dt>\n <dd className=\"mt-1 text-text-primary\">{formatNumber(circle.memberCount)}</dd>\n </div>\n <div>\n <dt className=\"text-text-tertiary\">Created</dt>\n <dd className=\"mt-1 text-text-primary\">{formatDate(circle.createdAt)}</dd>\n </div>\n </dl>\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Create channel dialog (local — only used here)\n// ---------------------------------------------------------------------------\n\nconst CHANNEL_TYPES: ComChannelType[] = ['TEXT', 'FORUM', 'ANNOUNCEMENT', 'VOICE'];\n\nfunction CreateChannelDialog({\n circleId,\n open,\n onOpenChange,\n onCreated,\n}: {\n circleId: string;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCreated?: (channel: ComChannel) => void;\n}) {\n const createChannel = useComCreateChannel();\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [type, setType] = useState<ComChannelType>('TEXT');\n const [error, setError] = useState<string | null>(null);\n\n const submit = async () => {\n if (!name.trim()) {\n setError('Channel name is required.');\n return;\n }\n try {\n const created = await createChannel.mutateAsync({\n circleId,\n name: name.trim(),\n slug: slugify(name),\n description: description.trim() || null,\n type,\n });\n notify.success('Channel created', `#${created.name}`);\n onOpenChange(false);\n setName('');\n setDescription('');\n setType('TEXT');\n setError(null);\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create a channel</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Channels organise conversations by topic or format.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"flex flex-col gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"channel-name\">Name</Label>\n <Input\n id=\"channel-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"general\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Type</Label>\n <Select value={type} onValueChange={(v) => setType(v as ComChannelType)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHANNEL_TYPES.map((tp) => (\n <SelectItem key={tp} value={tp}>\n {formatEnumLabel(tp)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"channel-desc\">Description</Label>\n <Textarea\n id=\"channel-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n />\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={createChannel.isPending}>\n Create channel\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Create poll dialog (local — only used here)\n// ---------------------------------------------------------------------------\n\nfunction CreatePollDialog({\n circleId,\n open,\n onOpenChange,\n}: {\n circleId: string;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}) {\n const createPoll = useComCreatePoll();\n const [question, setQuestion] = useState('');\n const [options, setOptions] = useState<string[]>(['', '']);\n const [allowMultiple, setAllowMultiple] = useState(false);\n const [isAnonymous, setIsAnonymous] = useState(false);\n const [closesAt, setClosesAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n // Lower bound for the datetime-local picker: \"now\" in the viewer's local zone.\n const minCloses = useMemo(() => {\n const now = new Date();\n now.setMinutes(now.getMinutes() - now.getTimezoneOffset());\n return now.toISOString().slice(0, 16);\n }, []);\n\n const reset = () => {\n setQuestion('');\n setOptions(['', '']);\n setAllowMultiple(false);\n setIsAnonymous(false);\n setClosesAt('');\n setError(null);\n };\n\n const submit = async () => {\n const cleaned = options.map((o) => o.trim()).filter(Boolean);\n if (!question.trim()) {\n setError('A question is required.');\n return;\n }\n if (cleaned.length < 2) {\n setError('Add at least two options.');\n return;\n }\n try {\n await createPoll.mutateAsync({\n circleId,\n question: question.trim(),\n options: cleaned,\n allowMultiple,\n isAnonymous,\n closesAt: closesAt ? new Date(closesAt).toISOString() : null,\n });\n notify.success('Poll created');\n onOpenChange(false);\n reset();\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create a poll</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Gather the circle&apos;s opinion.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"flex flex-col gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"poll-q\">Question</Label>\n <Input\n id=\"poll-q\"\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"What should we build next?\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Options</Label>\n <div className=\"flex flex-col gap-2\">\n {options.map((opt, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <Input\n value={opt}\n onChange={(e) =>\n setOptions((prev) => prev.map((o, idx) => (idx === i ? e.target.value : o)))\n }\n placeholder={`Option ${i + 1}`}\n />\n {options.length > 2 && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setOptions((prev) => prev.filter((_, idx) => idx !== i))}\n aria-label={`Remove option ${i + 1}`}\n >\n ✕\n </Button>\n )}\n </div>\n ))}\n {options.length < 8 && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOptions((prev) => [...prev, ''])}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add option\n </Button>\n )}\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"poll-closes\">Closes at</Label>\n <Input\n id=\"poll-closes\"\n type=\"datetime-local\"\n min={minCloses}\n value={closesAt}\n onChange={(e) => setClosesAt(e.target.value)}\n />\n <p className=\"text-xs text-text-tertiary\">\n Optional — leave empty to keep voting open indefinitely.\n </p>\n </div>\n\n <div className=\"flex flex-col divide-y divide-border-seam rounded-xl border border-border-seam\">\n <div className=\"flex items-center justify-between gap-4 px-3 py-3\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"poll-multi\">Allow multiple choices</Label>\n <p className=\"text-xs text-text-tertiary\">Let voters pick more than one option.</p>\n </div>\n <Switch id=\"poll-multi\" checked={allowMultiple} onCheckedChange={setAllowMultiple} />\n </div>\n <div className=\"flex items-center justify-between gap-4 px-3 py-3\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"poll-anon\">Anonymous voting</Label>\n <p className=\"text-xs text-text-tertiary\">Hide who voted for each option.</p>\n </div>\n <Switch id=\"poll-anon\" checked={isAnonymous} onCheckedChange={setIsAnonymous} />\n </div>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={createPoll.isPending}>\n Create poll\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqFA,IAAM,KAAoD;CACxD,MAAM;CACN,OAAO;CACP,cAAc;CACd,OAAO;AACT,GAEM,KAAiC,CAAC,SAAS,OAAO,GAClD,IAAgC;CAAC;CAAS;CAAS;CAAa;AAAQ,GACxE,KAAY;AAElB,SAAgB,KAAmB;CACjC,IAAM,EAAE,UAAO,GAA0B,GACnC,EAAE,gBAAa,kBAAe,EAAiB,GAC/C,EAAE,MAAM,GAAQ,cAAW,YAAS,eAAY,EAAa,CAAE,GAC/D,IAAO,EAAiB,GACxB,IAAQ,EAAkB,GAC1B,CAAC,GAAK,KAAU,EAAiB,UAAU,GAE3C,IAAe,QACb,GAAQ,aAAa,MAAM,MAAM,EAAE,aAAa,GAAa,MAAM,EAAE,WAAW,QAAQ,GAC9F,CAAC,GAAQ,CAAW,CACtB,GACM,IAAW,EAAQ,GACnB,IAAY,IAAe,GAAc,SAAS,EAAa,IAAI,IAAI;CAE7E,IAAI,GACF,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,kBAAmB,CAAA;CAC5B,CAAA;CAGhB,IAAI,KAAW,CAAC,GACd,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAQ;EACxB,CAAA;CACS,CAAA;CAIhB,IAAM,KAAgB,EAAO,aAAa,QAAQ,MAAM,EAAE,WAAW,QAAQ,KAAK,CAAC,GAC7E,IAA0B;EAC9B;GACE,OAAO;GACP,OAAO;GACP,MAAM,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;GACjC,OAAO,EAAO,UAAU,UAAU;EACpC;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;GAClC,OAAO,GAAc;EACvB;EACA;GAAE,OAAO;GAAU,OAAO;GAAU,MAAM,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;EAAE;EAC/E;GAAE,OAAO;GAAS,OAAO;GAAS,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;EAAE;EAC1E;GAAE,OAAO;GAAS,OAAO;GAAS,MAAM,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA;EAAE;CACvE;CAEA,OACE,kBAAC,GAAD;EACE,OACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD;IAAQ,MAAM,EAAO;IAAM,MAAK;IAAK,QAAQ,EAAO,QAAQ;IAAI,MAAM,EAAO;IAAI,MAAA;GAAM,CAAA,GACvF,kBAAC,IAAD;IAAc,IAAG;IAAO,WAAU;cAC/B,EAAO;GACI,CAAA,CACV;;EAER,aAAa,EAAO,SAAS,KAAA;EAC7B,QAAA;EACA,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAK,eAAe,EAAW,SAAS;cAArE,CACE,kBAAC,IAAD,EAAW,WAAU,iBAAkB,CAAA,GAAC,OAClC;OACP,IACC,kBAAC,GAAD;IACE,SAAQ;IACR,eACE,EACG,YAAY,EAAO,EAAE,EACrB,WAAW,EAAO,QAAQ,aAAa,CAAC,EACxC,OAAO,MAAM,EAAO,MAAM,mBAAmB,EAAc,CAAC,CAAC,CAAC;IAEnE,UAAU,EAAM;cACjB;GAEO,CAAA,IACN,EAAO,WAAW,WACpB,kBAAC,GAAD;IACE,eACE,EACG,YAAY,EAAO,EAAE,EACrB,WAAW,EAAO,QAAQ,eAAe,CAAC,EAC1C,OAAO,MAAM,EAAO,MAAM,kBAAkB,EAAc,CAAC,CAAC,CAAC;IAElE,UAAU,EAAK;cAChB;GAEO,CAAA,IACN,IACD;;YA1CT;GA6CE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAa,QAAQ,EAAO,WAAa,CAAA;KACxC,EAAO,WAAW,YAAY,kBAAC,GAAD,EAAa,QAAQ,EAAO,OAAS,CAAA;KACnE,KAAgB,kBAAC,GAAD,EAAa,QAAQ,EAAa,KAAO,CAAA;KAC1D,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;OAAC;OAAE,EAAa,EAAO,WAAW;OAAE;MAC5D;;IACH;;GAEL,kBAAC,IAAD;IAAM,OAAO;IAAM,OAAO;IAAK,UAAU;IAAQ,WAAU;GAAQ,CAAA;GAElE,MAAQ,cACP,kBAAC,IAAD;IAAqB;IAAkB;IAAqB;GAAY,CAAA;GAEzE,MAAQ,aAAa,kBAAC,IAAD;IAAoB;IAAmB;GAAY,CAAA;GACxE,MAAQ,YAAY,kBAAC,IAAD;IAAmB;IAAkB;GAAW,CAAA;GACpE,MAAQ,WAAW,kBAAC,IAAD;IAAkB;IAAkB;IAAqB;GAAY,CAAA;GACxF,MAAQ,WAAW,kBAAC,IAAD,EAAkB,UAAS,CAAA;EACrC;;AAEhB;AAMA,SAAS,GAAY,EACnB,WACA,aACA,gBAKC;CACD,IAAM,IAAW,QACT,CAAC,GAAI,EAAO,YAAY,CAAC,CAAE,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,GACzE,CAAC,EAAO,QAAQ,CAClB,GACM,CAAC,GAAY,KAAiB,EAA6B,EAAS,IAAI,EAAE,GAC1E,CAAC,GAAY,KAAiB,EAAS,EAAK,GAC5C,IAAW,EAAS,MAAM,MAAM,EAAE,OAAO,CAAU,KAAK,EAAS;CA+BvE,OA7BI,EAAS,WAAW,IAEpB,kBAAA,IAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EACE,MAAM,kBAAC,GAAD,EAAM,WAAU,YAAa,CAAA;EACnC,OAAM;EACN,aACE,IACI,oEACA;EAEN,QACE,IACE,kBAAC,GAAD;GAAQ,eAAe,EAAc,EAAI;aAAzC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,cAC7B;OACN,KAAA;CAEP,CAAA,GACD,kBAAC,IAAD;EACE,UAAU,EAAO;EACjB,MAAM;EACN,cAAc;EACd,YAAY,MAAO,EAAc,EAAG,EAAE;CACvC,CAAA,CACD,EAAA,CAAA,IAKJ,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAmE;KAE7E,CAAA,GACH,KACC,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAc,EAAI;MACjC,cAAW;MACX,WAAU;gBAEV,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;KACrB,CAAA,CAEP;QACL,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAS,KAAK,MAAO;MACpB,IAAM,IAAO,GAAa,EAAG,OACvB,IAAS,EAAG,OAAO,GAAU;MACnC,OACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAc,EAAG,EAAE;OAClC,gBAAc,IAAS,SAAS,KAAA;OAChC,WAAW,EACT,gGACA,IACI,6GACA,qHACN;iBAVF,CAYE,kBAAC,GAAD,EACE,WAAW,EAAG,oBAAoB,IAAS,gBAAgB,oBAAoB,EAChF,CAAA,GACD,kBAAC,QAAD;QAAM,WAAU;kBAAY,EAAG;OAAW,CAAA,CACpC;SAfD,EAAG,EAeF;KAEZ,CAAC;IACE,CAAA,CACA;;GAGP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAQ,OAAO,GAAU;MAAI,eAAe;gBAA5C,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAa,aAAY,mBAAoB,CAAA,EAChC,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,GAAD;OAAwB,OAAO,EAAG;iBAAlC,CAAsC,KAClC,EAAG,IACK;SAFK,EAAG,EAER,CACb,EACY,CAAA,CACT;SACP,KACC,kBAAC,GAAD;MACE,SAAQ;MACR,MAAK;MACL,eAAe,EAAc,EAAI;MACjC,cAAW;gBAEX,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;KACrB,CAAA,CAEP;;GACF,CAAA;GAGL,kBAAC,OAAD;IAAK,WAAU;cACZ,KAAY,kBAAC,IAAD;KAAa,SAAS;KAAU,SAAS;KAAqB;IAAY,CAAA;GACpF,CAAA;GAEL,kBAAC,IAAD;IACE,UAAU,EAAO;IACjB,MAAM;IACN,cAAc;IACd,YAAY,MAAO,EAAc,EAAG,EAAE;GACvC,CAAA;EACE;;AAET;AAEA,SAAS,GAAY,EACnB,YACA,YACA,gBAKC;CACD,IAAM,IAAQ,EAAe,GACvB,EAAE,SAAM,cAAW,YAAS,eAAY,EAAY;EACxD,WAAW,EAAQ;EACnB,QAAQ;EACR,YAAY;GAAE,OAAO;GAAW,QAAQ,EAAM;EAAO;CACvD,CAAC,GACK,IAAQ,GAAM,SAAS,CAAC,GAGxB,IACJ,EAAQ,SAAS,UAAU,KAAQ,EAAQ,SAAS,iBAAiB,IAAY;CAEnF,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,QACU;KACN,IAAM,IAAO,GAAa,EAAQ;KAClC,OACE,kBAAC,QAAD;MAAM,WAAU;gBACd,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;KACvB,CAAA;IAEV,GAAG,GACH,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA2C,EAAQ;KAAS,CAAA,GACzE,EAAQ,eACP,kBAAC,KAAD;MAAG,WAAU;gBAA+B,EAAQ;KAAe,CAAA,CAElE;MACF;;GAEJ,KACC,kBAAC,GAAD;IACE,WAAW,EAAQ;IACnB,aAAa,EAAQ;IACrB,gBAAgB,EAAM,MAAM;GAC7B,CAAA;GAGF,EAAQ,SAAS,kBAAkB,CAAC,KACnC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAW,WAAU,0CAA2C,CAAA,GAAC,yDAE9D;;GAGN,EAAQ,SAAS,WAChB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAK,WAAU,0CAA2C,CAAA,GAAC,mEAExD;;GAGN,IACC,kBAAC,GAAD,EAAa,OAAM,iBAAkB,CAAA,IACnC,IACF,kBAAC,GAAD;IAAW,SAAQ;IAA0B,eAAe,EAAQ;GAAI,CAAA,IACtE,EAAM,WAAW,IACnB,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAe,WAAU,UAAW,CAAA;IAC1C,OAAM;IACN,aACE,IAAW,0CAA0C;GAExD,CAAA,IAED,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAM,KAAK,MACV,kBAAC,GAAD,EAA8B,QAAO,GAAtB,EAAK,EAAiB,CACtC,GACD,kBAAC,GAAD;KACE,MAAM,EAAM;KACZ,UAAU;KACV,WAAW,EAAM;KACjB,SAAS,GAAM,SAAS,WAAW;KACnC,eAAe,MAAW,EAAM,aAAa,GAAQ,GAAM,SAAS,MAAM;IAC3E,CAAA,CACE;;EAEJ;;AAET;AAMA,SAAS,GAAW,EAAE,WAAQ,gBAAwD;CACpF,IAAM,IAAU,EAAoB,GAC9B,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,IAAU,QAEZ,CAAC,GAAI,EAAO,eAAe,CAAC,CAAE,EAAE,MAC7B,GAAG,MAAM,EAAa,QAAQ,EAAE,IAAI,IAAI,EAAa,QAAQ,EAAE,IAAI,CACtE,GACF,CAAC,EAAO,WAAW,CACrB,GAIM,IAAW,QAAc;EAC7B,IAAM,IAAI,EAAM,KAAK,EAAE,YAAY;EAEnC,OADK,IACE,EAAQ,QACZ,MACC,EAAgB,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,KAChD,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,KAC/B,EAAgB,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,KAClD,EAAE,OAAO,YAAY,EAAE,SAAS,CAAC,CACrC,IAPe;CAQjB,GAAG,CAAC,GAAS,CAAK,CAAC;CAEnB,IAAI,EAAQ,WAAW,GACrB,OAAO,kBAAC,GAAD;EAAY,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;EAAG,OAAM;CAAkB,CAAA;CAGlF,IAAM,IAAa,OAAO,GAAsB,MAAwB;EACtE,IAAI;GAEF,AADA,MAAM,EAAQ,YAAY;IAAE;IAAc;IAAM,UAAU,EAAO;GAAG,CAAC,GACrE,EAAO,QAAQ,cAAc;EAC/B,SAAS,GAAK;GACZ,EAAO,MAAM,yBAAyB,EAAc,CAAG,CAAC;EAC1D;CACF,GAEM,IAAW,IACb,mDACA;CAEJ,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,IAAD,EAAQ,WAAU,0FAA2F,CAAA,GAC7G,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAS,EAAE,OAAO,KAAK;KACxC,aAAY;KACZ,WAAU;KACV,cAAW;IACZ,CAAA,CACE;OACL,kBAAC,QAAD;IAAM,WAAU;cACb,EAAS,WAAW,EAAQ,SACzB,GAAG,EAAa,EAAQ,MAAM,EAAE,SAAS,EAAQ,WAAW,IAAI,KAAK,QACrE,GAAG,EAAa,EAAS,MAAM,EAAE,MAAM,EAAa,EAAQ,MAAM;GAClE,CAAA,CACH;MAEL,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IACE,WAAW,EACT,kJACA,CACF;cAJF;KAME,kBAAC,QAAD,EAAA,UAAM,SAAY,CAAA;KAClB,kBAAC,QAAD,EAAA,UAAM,SAAY,CAAA;KAClB,kBAAC,QAAD,EAAA,UAAM,OAAU,CAAA;KACf,KAAa,kBAAC,QAAD,EAAA,UAAM,cAAiB,CAAA;IAClC;OAEJ,EAAS,WAAW,IACnB,kBAAC,OAAD;IAAK,WAAU;cAAf;KAAoE;KAC/C,EAAM,KAAK;KAAE;IAC7B;QAEL,EAAS,KAAK,MACZ,kBAAC,OAAD;IAEE,WAAW,EACT,kKACA,CACF;cALF,CAOE,kBAAC,GAAD;KAAgB,UAAU,EAAE;KAAU,SAAQ;KAAM,MAAK;KAAK,cAAA;IAAc,CAAA,GAC5E,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAE,OAAS,CAAA,EAC7B,CAAA;MACL,kBAAC,OAAD,EAAA,UACE,kBAAC,GAAD,EAAa,QAAQ,EAAE,KAAO,CAAA,EAC3B,CAAA;MACJ,KACC,kBAAC,GAAD;OACE,OAAO,EAAE;OACT,gBAAgB,MAAM,EAAW,EAAE,IAAI,CAAkB;iBAF3D,CAIE,kBAAC,GAAD;QAAe,WAAU;kBACvB,kBAAC,GAAD,CAAc,CAAA;OACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAa,KAAK,MACjB,kBAAC,GAAD;QAAoB,OAAO;kBACxB,EAAgB,CAAC;OACR,GAFK,CAEL,CACb,EACY,CAAA,CACT;;KAEP;MACF;MAhCE,EAAE,EAgCJ,CACN,CAEA;IACF;;AAET;AAMA,SAAS,GAAU,EAAE,WAAQ,eAAsD;CACjF,IAAM,EAAE,kBAAe,EAAiB,GAClC,EAAE,SAAM,cAAW,YAAS,eAAY,EAAa,EAAE,UAAU,EAAO,GAAG,CAAC,GAC5E,CAAC,GAAY,KAAiB,EAAS,EAAK,GAC5C,IAAS,GAAM,SAAS,CAAC;CAE/B,OACE,kBAAC,OAAD,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;aACZ,KACC,kBAAC,GAAD;IAAQ,eAAe,EAAc,EAAI;cAAzC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,YAC7B;;EAEP,CAAA;EACJ,IACC,kBAAC,GAAD,EAAa,OAAM,kBAAmB,CAAA,IACpC,IACF,kBAAC,GAAD,EAAW,eAAe,EAAQ,EAAI,CAAA,IACpC,EAAO,WAAW,IACpB,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;GACzC,OAAM;GACN,aACE,IAAW,8CAA8C;GAE3D,QACE,IACE,kBAAC,GAAD;IAAQ,eAAe,EAAc,EAAI;cAAzC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,YAC7B;QACN,KAAA;EAEP,CAAA,IAED,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAO,KAAK,MACX,kBAAC,IAAD;IAAuB,OAAO;IAAI,cAAc,EAAW,UAAU,EAAG,IAAI;GAAI,GAAhE,EAAG,EAA6D,CACjF;EACE,CAAA;EAEP,kBAAC,IAAD;GAAmB,UAAU,EAAO;GAAI,MAAM;GAAY,cAAc;EAAgB,CAAA;CACrF,EAAA,CAAA;AAET;AAMA,SAAS,GAAS,EAChB,WACA,aACA,gBAKC;CACD,IAAM,EAAE,MAAM,GAAO,cAAW,YAAS,eAAY,EAAY,EAAO,EAAE,GACpE,CAAC,GAAY,KAAiB,EAAS,EAAK;CAElD,OACE,kBAAC,OAAD,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;aACZ,KACC,kBAAC,GAAD;IAAQ,eAAe,EAAc,EAAI;cAAzC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,WAC7B;;EAEP,CAAA;EACJ,IACC,kBAAC,GAAD,EAAa,OAAM,iBAAkB,CAAA,IACnC,IACF,kBAAC,GAAD,EAAW,eAAe,EAAQ,EAAI,CAAA,KACnC,KAAS,CAAC,GAAG,WAAW,IAC3B,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;GACtC,OAAM;GACN,aACE,IAAW,2CAA2C;GAExD,QACE,IACE,kBAAC,GAAD;IAAQ,eAAe,EAAc,EAAI;cAAzC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,WAC7B;QACN,KAAA;EAEP,CAAA,IAED,kBAAC,OAAD;GAAK,WAAU;cACX,KAAS,CAAC,GAAG,KAAK,MAClB,kBAAC,IAAD;IAA8B;IAAiB;GAAY,GAA5C,EAAK,EAAuC,CAC5D;EACE,CAAA;EAEP,kBAAC,IAAD;GAAkB,UAAU,EAAO;GAAI,MAAM;GAAY,cAAc;EAAgB,CAAA;CACpF,EAAA,CAAA;AAET;AAMA,SAAS,GAAS,EAAE,aAAiC;CACnD,OACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;IAAqB,CAAA;IAC7E,kBAAC,KAAD;KAAG,WAAU;eACV,EAAO,eAAe;IACtB,CAAA;IACF,EAAO,KAAK,SAAS,KACpB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAO,KAAK,KAAK,MAChB,kBAAC,QAAD;MAEE,WAAU;gBAFZ,CAGC,KACG,CACE;QAJC,CAID,CACP;IACE,CAAA;IAEP,kBAAC,MAAD;KAAI,WAAU;eAAd;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAqB;MAAc,CAAA,GACjD,kBAAC,MAAD;OAAI,WAAU;iBAA0B,EAAgB,EAAO,UAAU;MAAM,CAAA,CAC5E,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAqB;MAAU,CAAA,GAC7C,kBAAC,MAAD;OAAI,WAAU;iBAA0B,EAAgB,EAAO,MAAM;MAAM,CAAA,CACxE,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAqB;MAAW,CAAA,GAC9C,kBAAC,MAAD;OAAI,WAAU;iBAA0B,EAAa,EAAO,WAAW;MAAM,CAAA,CAC1E,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAqB;MAAW,CAAA,GAC9C,kBAAC,MAAD;OAAI,WAAU;iBAA0B,EAAW,EAAO,SAAS;MAAM,CAAA,CACtE,EAAA,CAAA;KACH;;GACD;;CACF,CAAA;AAET;AAMA,IAAM,KAAkC;CAAC;CAAQ;CAAS;CAAgB;AAAO;AAEjF,SAAS,GAAoB,EAC3B,aACA,SACA,iBACA,gBAMC;CACD,IAAM,IAAgB,EAAoB,GACpC,CAAC,GAAM,KAAW,EAAS,EAAE,GAC7B,CAAC,GAAa,KAAkB,EAAS,EAAE,GAC3C,CAAC,GAAM,KAAW,EAAyB,MAAM,GACjD,CAAC,GAAO,KAAY,EAAwB,IAAI;CA2BtD,OACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,mBAAuC,CAAA,GAC9D,kBAAC,GAAD,EAAA,UAA6B,sDAEA,CAAA,CACP,EAAA,CAAA;IACxB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAe;OAAW,CAAA,GACzC,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAQ,EAAE,OAAO,KAAK;QACvC,aAAY;OACb,CAAA,CACE;;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,OAAW,CAAA,GAClB,kBAAC,GAAD;QAAQ,OAAO;QAAM,gBAAgB,MAAM,EAAQ,CAAmB;kBAAtE,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,CAAc,CAAA,EACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,GAAc,KAAK,MAClB,kBAAC,GAAD;SAAqB,OAAO;mBACzB,EAAgB,CAAE;QACT,GAFK,CAEL,CACb,EACY,CAAA,CACT;SACL;;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAe;OAAkB,CAAA,GAChD,kBAAC,IAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;QAC9C,MAAM;OACP,CAAA,CACE;;MACJ,KAAS,kBAAC,KAAD;OAAG,WAAU;iBAAkC;MAAS,CAAA;KAC/D;;IACL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAa,EAAK;eAAG;IAEtD,CAAA,GACR,kBAAC,GAAD;KAAQ,SAAS,YA1EE;MACzB,IAAI,CAAC,EAAK,KAAK,GAAG;OAChB,EAAS,2BAA2B;OACpC;MACF;MACA,IAAI;OACF,IAAM,IAAU,MAAM,EAAc,YAAY;QAC9C;QACA,MAAM,EAAK,KAAK;QAChB,MAAM,GAAQ,CAAI;QAClB,aAAa,EAAY,KAAK,KAAK;QACnC;OACF,CAAC;OAOD,AANA,EAAO,QAAQ,mBAAmB,IAAI,EAAQ,MAAM,GACpD,EAAa,EAAK,GAClB,EAAQ,EAAE,GACV,EAAe,EAAE,GACjB,EAAQ,MAAM,GACd,EAAS,IAAI,GACb,IAAY,CAAO;MACrB,SAAS,GAAK;OACZ,EAAS,EAAc,CAAG,CAAC;MAC7B;KACF;KAmDiC,UAAU,EAAc;eAAW;IAEpD,CAAA,CACc,EAAA,CAAA;GACD;;CACT,CAAA;AAEtB;AAMA,SAAS,GAAiB,EACxB,aACA,SACA,mBAKC;CACD,IAAM,IAAa,EAAiB,GAC9B,CAAC,GAAU,KAAe,EAAS,EAAE,GACrC,CAAC,GAAS,KAAc,EAAmB,CAAC,IAAI,EAAE,CAAC,GACnD,CAAC,GAAe,KAAoB,EAAS,EAAK,GAClD,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,CAAC,GAAU,KAAe,EAAS,EAAE,GACrC,CAAC,GAAO,KAAY,EAAwB,IAAI,GAGhD,IAAY,QAAc;EAC9B,IAAM,oBAAM,IAAI,KAAK;EAErB,OADA,EAAI,WAAW,EAAI,WAAW,IAAI,EAAI,kBAAkB,CAAC,GAClD,EAAI,YAAY,EAAE,MAAM,GAAG,EAAE;CACtC,GAAG,CAAC,CAAC,GAEC,UAAc;EAMlB,AALA,EAAY,EAAE,GACd,EAAW,CAAC,IAAI,EAAE,CAAC,GACnB,EAAiB,EAAK,GACtB,EAAe,EAAK,GACpB,EAAY,EAAE,GACd,EAAS,IAAI;CACf;CA6BA,OACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,gBAAoC,CAAA,GAC3D,kBAAC,GAAD,EAAA,UAA6B,+BAEA,CAAA,CACP,EAAA,CAAA;IACxB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAS;OAAe,CAAA,GACvC,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAY,EAAE,OAAO,KAAK;QAC3C,aAAY;OACb,CAAA,CACE;;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,GACrB,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,EAAQ,KAAK,GAAK,MACjB,kBAAC,OAAD;SAAa,WAAU;mBAAvB,CACE,kBAAC,GAAD;UACE,OAAO;UACP,WAAW,MACT,GAAY,MAAS,EAAK,KAAK,GAAG,MAAS,MAAQ,IAAI,EAAE,OAAO,QAAQ,CAAE,CAAC;UAE7E,aAAa,UAAU,IAAI;SAC5B,CAAA,GACA,EAAQ,SAAS,KAChB,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,eAAe,GAAY,MAAS,EAAK,QAAQ,GAAG,MAAQ,MAAQ,CAAC,CAAC;UACtE,cAAY,iBAAiB,IAAI;oBAClC;SAEO,CAAA,CAEP;WAlBK,CAkBL,CACN,GACA,EAAQ,SAAS,KAChB,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,eAAe,GAAY,MAAS,CAAC,GAAG,GAAM,EAAE,CAAC;mBAHnD,CAKE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,aAC7B;UAEP;SACF;;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAO,SAAQ;mBAAc;QAAgB,CAAA;QAC7C,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,KAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAY,EAAE,OAAO,KAAK;QAC5C,CAAA;QACD,kBAAC,KAAD;SAAG,WAAU;mBAA6B;QAEvC,CAAA;OACA;;MAEL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAa;SAA6B,CAAA,GACzD,kBAAC,KAAD;UAAG,WAAU;oBAA6B;SAAwC,CAAA,CAC/E;YACL,kBAAC,GAAD;SAAQ,IAAG;SAAa,SAAS;SAAe,iBAAiB;QAAmB,CAAA,CACjF;WACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAY;SAAuB,CAAA,GAClD,kBAAC,KAAD;UAAG,WAAU;oBAA6B;SAAkC,CAAA,CACzE;YACL,kBAAC,GAAD;SAAQ,IAAG;SAAY,SAAS;SAAa,iBAAiB;QAAiB,CAAA,CAC5E;SACF;;MACJ,KAAS,kBAAC,KAAD;OAAG,WAAU;iBAAkC;MAAS,CAAA;KAC/D;;IACL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAa,EAAK;eAAG;IAEtD,CAAA,GACR,kBAAC,GAAD;KAAQ,SAAS,YArHE;MACzB,IAAM,IAAU,EAAQ,KAAK,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;MAC3D,IAAI,CAAC,EAAS,KAAK,GAAG;OACpB,EAAS,yBAAyB;OAClC;MACF;MACA,IAAI,EAAQ,SAAS,GAAG;OACtB,EAAS,2BAA2B;OACpC;MACF;MACA,IAAI;OAWF,AAVA,MAAM,EAAW,YAAY;QAC3B;QACA,UAAU,EAAS,KAAK;QACxB,SAAS;QACT;QACA;QACA,UAAU,IAAW,IAAI,KAAK,CAAQ,EAAE,YAAY,IAAI;OAC1D,CAAC,GACD,EAAO,QAAQ,cAAc,GAC7B,EAAa,EAAK,GAClB,EAAM;MACR,SAAS,GAAK;OACZ,EAAS,EAAc,CAAG,CAAC;MAC7B;KACF;KA4FiC,UAAU,EAAW;eAAW;IAEjD,CAAA,CACc,EAAA,CAAA;GACD;;CACT,CAAA;AAEtB"}
1
+ {"version":3,"file":"CircleDetailPage.js","names":[],"sources":["../../../../src/comradecircle/pages/circles/CircleDetailPage.tsx"],"sourcesContent":["/**\n * CircleDetailPage — a single circle: channels & posts, members, events, polls.\n * @module comradecircle/pages/circles\n *\n * Reads `comCircle` (channels + memberships), `comListPosts`, `comListEvents`,\n * `comListPolls`. Writes join/leave, create channel, create poll, set member\n * role (via the shared cards/dialogs). The channel post feed is the heart of the\n * page; roles gate management actions (OWNER/ADMIN manage; members post).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Users,\n Hash,\n CalendarDays,\n BarChart3,\n Info,\n Plus,\n Megaphone,\n MessageCircle,\n Mic,\n MessagesSquare,\n Search,\n} from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Switch,\n GradientText,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../../utils/cn';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Avatar } from '../../components/Avatar';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Tabs, type TabItem } from '../../components/Tabs';\nimport { MemberIdentity } from '../../components/MemberIdentity';\nimport { PostCard } from '../../components/PostCard';\nimport { PostComposer } from '../../components/PostComposer';\nimport { EventCard } from '../../components/EventCard';\nimport { PollCard } from '../../components/PollCard';\nimport { CreateEventDialog } from '../../components/CreateEventDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useComCircle,\n useComPosts,\n useComEvents,\n useComPolls,\n useComJoinCircle,\n useComLeaveCircle,\n useComCreateChannel,\n useComCreatePoll,\n useComSetMemberRole,\n useCursorPager,\n} from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatDate, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport { slugify } from '../../utils/slug';\nimport { notify, describeError } from '../../lib/toast';\nimport type {\n ComChannel,\n ComChannelType,\n ComCircle,\n ComMembership,\n ComMemberRole,\n} from '../../types';\n\ntype TabKey = 'channels' | 'members' | 'events' | 'polls' | 'about';\n\nconst CHANNEL_ICON: Record<ComChannelType, typeof Hash> = {\n TEXT: Hash,\n FORUM: MessagesSquare,\n ANNOUNCEMENT: Megaphone,\n VOICE: Mic,\n};\n\nconst MANAGER_ROLES: ComMemberRole[] = ['OWNER', 'ADMIN'];\nconst ROLE_OPTIONS: ComMemberRole[] = ['OWNER', 'ADMIN', 'MODERATOR', 'MEMBER'];\nconst POST_PAGE = 15;\n\nexport function CircleDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { currentUser, navigateTo } = useComradeCircle();\n const { data: circle, isLoading, isError, refetch } = useComCircle(id);\n const join = useComJoinCircle();\n const leave = useComLeaveCircle();\n const [tab, setTab] = useState<TabKey>('channels');\n\n const myMembership = useMemo<ComMembership | undefined>(\n () => circle?.memberships?.find((m) => m.memberId === currentUser?.id && m.status === 'ACTIVE'),\n [circle, currentUser]\n );\n const isMember = Boolean(myMembership);\n const canManage = myMembership ? MANAGER_ROLES.includes(myMembership.role) : false;\n\n if (isLoading) {\n return (\n <PageLayout title=\"Circle\">\n <LoadingView label=\"Loading circle…\" />\n </PageLayout>\n );\n }\n if (isError || !circle) {\n return (\n <PageLayout title=\"Circle\">\n <ErrorView\n title=\"Circle not found\"\n message=\"This circle may have been archived or you may not have access.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n const activeMembers = circle.memberships?.filter((m) => m.status === 'ACTIVE') ?? [];\n const tabs: TabItem<TabKey>[] = [\n {\n value: 'channels',\n label: 'Channels',\n icon: <Hash className=\"h-4 w-4\" />,\n count: circle.channels?.length ?? 0,\n },\n {\n value: 'members',\n label: 'Members',\n icon: <Users className=\"h-4 w-4\" />,\n count: activeMembers.length,\n },\n { value: 'events', label: 'Events', icon: <CalendarDays className=\"h-4 w-4\" /> },\n { value: 'polls', label: 'Polls', icon: <BarChart3 className=\"h-4 w-4\" /> },\n { value: 'about', label: 'About', icon: <Info className=\"h-4 w-4\" /> },\n ];\n\n return (\n <PageLayout\n title={\n <span className=\"flex items-center gap-3\">\n <Avatar name={circle.name} size=\"lg\" fileId={circle.avatar?.id} seed={circle.id} ring />\n <GradientText as=\"span\" className=\"text-2xl font-semibold tracking-tight\">\n {circle.name}\n </GradientText>\n </span>\n }\n description={circle.topic ?? undefined}\n aurora\n actions={\n <div className=\"flex items-center gap-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('circles')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n {isMember ? (\n <Button\n variant=\"outline\"\n onClick={() =>\n leave\n .mutateAsync(circle.id)\n .then(() => notify.success('Left circle'))\n .catch((e) => notify.error('Could not leave', describeError(e)))\n }\n disabled={leave.isPending}\n >\n Leave\n </Button>\n ) : circle.status === 'ACTIVE' ? (\n <Button\n onClick={() =>\n join\n .mutateAsync(circle.id)\n .then(() => notify.success('Joined circle'))\n .catch((e) => notify.error('Could not join', describeError(e)))\n }\n disabled={join.isPending}\n >\n Join circle\n </Button>\n ) : null}\n </div>\n }\n >\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n <StatusBadge status={circle.visibility} />\n {circle.status !== 'ACTIVE' && <StatusBadge status={circle.status} />}\n {myMembership && <StatusBadge status={myMembership.role} />}\n <span className=\"inline-flex items-center gap-1 text-sm text-text-tertiary\">\n <Users className=\"h-4 w-4\" /> {formatNumber(circle.memberCount)} members\n </span>\n </div>\n\n <Tabs items={tabs} value={tab} onChange={setTab} className=\"mb-5\" />\n\n {tab === 'channels' && (\n <ChannelsTab circle={circle} isMember={isMember} canManage={canManage} />\n )}\n {tab === 'members' && <MembersTab circle={circle} canManage={canManage} />}\n {tab === 'events' && <EventsTab circle={circle} isMember={isMember} />}\n {tab === 'polls' && <PollsTab circle={circle} isMember={isMember} canManage={canManage} />}\n {tab === 'about' && <AboutTab circle={circle} />}\n </PageLayout>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Channels tab — channel rail + selected channel post feed\n// ---------------------------------------------------------------------------\n\nfunction ChannelsTab({\n circle,\n isMember,\n canManage,\n}: {\n circle: ComCircle;\n isMember: boolean;\n canManage: boolean;\n}) {\n const channels = useMemo(\n () => [...(circle.channels ?? [])].sort((a, b) => a.position - b.position),\n [circle.channels]\n );\n const [selectedId, setSelectedId] = useState<string | undefined>(channels[0]?.id);\n const [createOpen, setCreateOpen] = useState(false);\n const selected = channels.find((c) => c.id === selectedId) ?? channels[0];\n\n if (channels.length === 0) {\n return (\n <>\n <EmptyState\n icon={<Hash className=\"h-10 w-10\" />}\n title=\"No channels yet\"\n description={\n canManage\n ? 'Create the first channel to start conversations in this circle.'\n : 'This circle has no channels yet. Check back soon.'\n }\n action={\n canManage ? (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New channel\n </Button>\n ) : undefined\n }\n />\n <CreateChannelDialog\n circleId={circle.id}\n open={createOpen}\n onOpenChange={setCreateOpen}\n onCreated={(ch) => setSelectedId(ch.id)}\n />\n </>\n );\n }\n\n return (\n <div className=\"grid gap-5 lg:grid-cols-[220px_1fr]\">\n {/* Channel rail (desktop) */}\n <aside className=\"hidden lg:block\">\n <div className=\"mb-2 flex items-center justify-between\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Channels\n </h3>\n {canManage && (\n <button\n type=\"button\"\n onClick={() => setCreateOpen(true)}\n aria-label=\"New channel\"\n className=\"rounded-md p-1 text-text-tertiary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <Plus className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n <nav className=\"flex flex-col gap-0.5\">\n {channels.map((ch) => {\n const Icon = CHANNEL_ICON[ch.type];\n const active = ch.id === selected?.id;\n const isAnnouncement = ch.type === 'ANNOUNCEMENT';\n return (\n <button\n key={ch.id}\n type=\"button\"\n onClick={() => setSelectedId(ch.id)}\n aria-current={active ? 'true' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-lg border-l-2 px-2.5 py-1.5 text-left text-sm transition-all',\n active\n ? 'border-[color:var(--color-accent)] bg-[var(--color-accent-soft)] font-medium text-text-primary shadow-sm'\n : isAnnouncement\n ? 'border-transparent font-medium text-accent hover:bg-bg-sunken motion-safe:hover:translate-x-0.5'\n : 'border-transparent text-text-secondary hover:bg-bg-sunken hover:text-text-primary motion-safe:hover:translate-x-0.5'\n )}\n >\n <Icon\n className={cn(\n 'h-4 w-4 shrink-0',\n active || isAnnouncement ? 'text-accent' : 'text-text-tertiary'\n )}\n />\n <span className=\"truncate\">{ch.name}</span>\n </button>\n );\n })}\n </nav>\n </aside>\n\n {/* Channel picker (mobile) */}\n <div className=\"lg:hidden\">\n <div className=\"flex items-center gap-2\">\n <Select value={selected?.id} onValueChange={setSelectedId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select a channel\" />\n </SelectTrigger>\n <SelectContent>\n {channels.map((ch) => (\n <SelectItem key={ch.id} value={ch.id}>\n #{ch.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {canManage && (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => setCreateOpen(true)}\n aria-label=\"New channel\"\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n </div>\n\n {/* Selected channel feed */}\n <div className=\"min-w-0\">\n {selected && <ChannelFeed channel={selected} canPost={isMember} canManage={canManage} />}\n </div>\n\n <CreateChannelDialog\n circleId={circle.id}\n open={createOpen}\n onOpenChange={setCreateOpen}\n onCreated={(ch) => setSelectedId(ch.id)}\n />\n </div>\n );\n}\n\nfunction ChannelFeed({\n channel,\n canPost,\n canManage,\n}: {\n channel: ComChannel;\n canPost: boolean;\n canManage: boolean;\n}) {\n const pager = useCursorPager();\n const { data, isLoading, isError, refetch } = useComPosts({\n channelId: channel.id,\n status: 'PUBLISHED',\n pagination: { limit: POST_PAGE, cursor: pager.cursor },\n });\n const posts = data?.items ?? [];\n const isAnnouncement = channel.type === 'ANNOUNCEMENT';\n // Voice channels never take text posts; announcements are manager-only; every\n // other channel type is open to any member.\n const canWrite = channel.type === 'VOICE' ? false : isAnnouncement ? canManage : canPost;\n\n // Broadcast layout (BOFF-3969): the mock lifts a single pinned — or, absent\n // a pin, the most recent — announcement into a prominent card above the\n // regular chronological list, only on the first page (later pages are pure\n // history, no featured re-lift).\n const featured =\n isAnnouncement && pager.page === 0 && posts.length > 0\n ? (posts.find((p) => p.isPinned) ?? posts[0])\n : undefined;\n const rest = featured ? posts.filter((p) => p.id !== featured.id) : posts;\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-center gap-3\">\n {(() => {\n const Icon = CHANNEL_ICON[channel.type];\n return (\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-accent\">\n <Icon className=\"h-5 w-5\" />\n </span>\n );\n })()}\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{channel.name}</h2>\n {isAnnouncement && (\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-accent\">\n <Megaphone className=\"h-3 w-3\" /> Broadcast\n </span>\n )}\n </div>\n {channel.description && (\n <p className=\"text-sm text-text-secondary\">{channel.description}</p>\n )}\n </div>\n </div>\n\n {canWrite && (\n <PostComposer\n channelId={channel.id}\n channelName={channel.name}\n onPosted={() => pager.reset()}\n variant={isAnnouncement ? 'announcement' : 'default'}\n />\n )}\n\n {isAnnouncement && !canWrite && (\n <div className=\"rounded-xl border border-dashed border-border-seam p-6 text-center text-sm text-text-secondary\">\n <Megaphone className=\"mx-auto mb-2 h-6 w-6 text-text-tertiary\" />\n Only circle managers can post in announcement channels.\n </div>\n )}\n\n {channel.type === 'VOICE' && (\n <div className=\"rounded-xl border border-dashed border-border-seam p-6 text-center text-sm text-text-secondary\">\n <Mic className=\"mx-auto mb-2 h-6 w-6 text-text-tertiary\" />\n Voice channels are for live audio. Text posting is disabled here.\n </div>\n )}\n\n {isLoading ? (\n <LoadingView label=\"Loading posts…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load posts.\" onRetry={() => refetch()} />\n ) : posts.length === 0 ? (\n <EmptyState\n icon={<MessageCircle className=\"h-9 w-9\" />}\n title=\"No posts yet\"\n description={\n canWrite ? 'Be the first to post in this channel.' : 'Nothing has been posted here yet.'\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {featured && (\n <div className=\"rounded-2xl border-2 border-[color:var(--color-accent)] bg-[var(--color-accent-soft)] p-1\">\n <div className=\"mb-1 flex items-center gap-1.5 px-3 pt-2 text-xs font-semibold uppercase tracking-wide text-accent\">\n <Megaphone className=\"h-3.5 w-3.5\" />\n {featured.isPinned ? 'Pinned announcement' : 'Latest announcement'}\n </div>\n <PostCard post={featured} />\n </div>\n )}\n {rest.length > 0 && (\n <div className=\"flex flex-col gap-4\">\n {featured && (\n <p className=\"text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Earlier announcements\n </p>\n )}\n {rest.map((post) => (\n <PostCard key={post.id} post={post} />\n ))}\n </div>\n )}\n <Pagination\n page={pager.page}\n pageSize={POST_PAGE}\n itemCount={posts.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Members tab\n// ---------------------------------------------------------------------------\n\nfunction MembersTab({ circle, canManage }: { circle: ComCircle; canManage: boolean }) {\n const setRole = useComSetMemberRole();\n const [query, setQuery] = useState('');\n const members = useMemo(\n () =>\n [...(circle.memberships ?? [])].sort(\n (a, b) => ROLE_OPTIONS.indexOf(a.role) - ROLE_OPTIONS.indexOf(b.role)\n ),\n [circle.memberships]\n );\n\n // Names resolve asynchronously inside MemberIdentity, so the filter works over\n // the data we hold here directly — role + membership status.\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return members;\n return members.filter(\n (m) =>\n formatEnumLabel(m.role).toLowerCase().includes(q) ||\n m.role.toLowerCase().includes(q) ||\n formatEnumLabel(m.status).toLowerCase().includes(q) ||\n m.status.toLowerCase().includes(q)\n );\n }, [members, query]);\n\n if (members.length === 0) {\n return <EmptyState icon={<Users className=\"h-9 w-9\" />} title=\"No members yet\" />;\n }\n\n const changeRole = async (membershipId: string, role: ComMemberRole) => {\n try {\n await setRole.mutateAsync({ membershipId, role, circleId: circle.id });\n notify.success('Role updated');\n } catch (err) {\n notify.error('Could not update role', describeError(err));\n }\n };\n\n const gridCols = canManage\n ? 'lg:grid-cols-[minmax(0,1fr)_120px_120px_190px]'\n : 'lg:grid-cols-[minmax(0,1fr)_120px_120px]';\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative w-full sm:max-w-xs\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Filter by role or status…\"\n className=\"pl-9\"\n aria-label=\"Filter members by role or status\"\n />\n </div>\n <span className=\"text-sm text-text-tertiary\">\n {filtered.length === members.length\n ? `${formatNumber(members.length)} member${members.length === 1 ? '' : 's'}`\n : `${formatNumber(filtered.length)} of ${formatNumber(members.length)}`}\n </span>\n </div>\n\n <div className=\"overflow-hidden rounded-2xl border border-border-seam bg-bg-surface\">\n <div\n className={cn(\n 'hidden border-b border-border-seam px-5 py-3 text-xs font-semibold uppercase tracking-wide text-text-tertiary lg:grid lg:items-center lg:gap-4',\n gridCols\n )}\n >\n <span>Member</span>\n <span>Status</span>\n <span>Role</span>\n {canManage && <span>Manage role</span>}\n </div>\n\n {filtered.length === 0 ? (\n <div className=\"px-5 py-10 text-center text-sm text-text-secondary\">\n No members match “{query.trim()}”.\n </div>\n ) : (\n filtered.map((m) => (\n <div\n key={m.id}\n className={cn(\n 'flex flex-col gap-3 border-b border-border-seam px-5 py-4 last:border-b-0 motion-safe:transition-colors hover:bg-bg-sunken/60 lg:grid lg:items-center lg:gap-4',\n gridCols\n )}\n >\n <MemberIdentity memberId={m.memberId} variant=\"row\" size=\"md\" showPresence />\n <div className=\"flex flex-wrap items-center gap-2 lg:contents\">\n <div>\n <StatusBadge status={m.status} />\n </div>\n <div>\n <StatusBadge status={m.role} />\n </div>\n {canManage && (\n <Select\n value={m.role}\n onValueChange={(v) => changeRole(m.id, v as ComMemberRole)}\n >\n <SelectTrigger className=\"h-9 w-40 lg:w-full\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ROLE_OPTIONS.map((r) => (\n <SelectItem key={r} value={r}>\n {formatEnumLabel(r)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n </div>\n ))\n )}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Events tab\n// ---------------------------------------------------------------------------\n\nfunction EventsTab({ circle, isMember }: { circle: ComCircle; isMember: boolean }) {\n const { navigateTo } = useComradeCircle();\n const { data, isLoading, isError, refetch } = useComEvents({ circleId: circle.id });\n const [createOpen, setCreateOpen] = useState(false);\n const events = data?.items ?? [];\n\n return (\n <div>\n <div className=\"mb-4 flex items-center justify-end\">\n {isMember && (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n )}\n </div>\n {isLoading ? (\n <LoadingView label=\"Loading events…\" />\n ) : isError ? (\n <ErrorView onRetry={() => refetch()} />\n ) : events.length === 0 ? (\n <EmptyState\n icon={<CalendarDays className=\"h-9 w-9\" />}\n title=\"No events scheduled\"\n description={\n isMember ? 'Schedule the first event for this circle.' : 'No upcoming events.'\n }\n action={\n isMember ? (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid gap-4 md:grid-cols-2\">\n {events.map((ev) => (\n <EventCard key={ev.id} event={ev} onOpen={() => navigateTo(`events/${ev.id}`)} />\n ))}\n </div>\n )}\n <CreateEventDialog circleId={circle.id} open={createOpen} onOpenChange={setCreateOpen} />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Polls tab\n// ---------------------------------------------------------------------------\n\nfunction PollsTab({\n circle,\n isMember,\n canManage,\n}: {\n circle: ComCircle;\n isMember: boolean;\n canManage: boolean;\n}) {\n const { data: polls, isLoading, isError, refetch } = useComPolls(circle.id);\n const [createOpen, setCreateOpen] = useState(false);\n\n return (\n <div>\n <div className=\"mb-4 flex items-center justify-end\">\n {isMember && (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New poll\n </Button>\n )}\n </div>\n {isLoading ? (\n <LoadingView label=\"Loading polls…\" />\n ) : isError ? (\n <ErrorView onRetry={() => refetch()} />\n ) : (polls ?? []).length === 0 ? (\n <EmptyState\n icon={<BarChart3 className=\"h-9 w-9\" />}\n title=\"No polls yet\"\n description={\n isMember ? 'Ask the circle a question with a poll.' : 'No polls have been created.'\n }\n action={\n isMember ? (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New poll\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-4\">\n {(polls ?? []).map((poll) => (\n <PollCard key={poll.id} poll={poll} canManage={canManage} />\n ))}\n </div>\n )}\n <CreatePollDialog circleId={circle.id} open={createOpen} onOpenChange={setCreateOpen} />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// About tab\n// ---------------------------------------------------------------------------\n\nfunction AboutTab({ circle }: { circle: ComCircle }) {\n return (\n <div className=\"mx-auto max-w-2xl\">\n <div className=\"rounded-2xl border border-border-seam bg-bg-surface p-6\">\n <h2 className=\"text-lg font-semibold text-text-primary\">About this circle</h2>\n <p className=\"mt-2 whitespace-pre-wrap text-sm text-text-secondary\">\n {circle.description || 'No description provided.'}\n </p>\n {circle.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {circle.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n <dl className=\"mt-6 grid grid-cols-2 gap-4 text-sm\">\n <div>\n <dt className=\"text-text-tertiary\">Visibility</dt>\n <dd className=\"mt-1 text-text-primary\">{formatEnumLabel(circle.visibility)}</dd>\n </div>\n <div>\n <dt className=\"text-text-tertiary\">Status</dt>\n <dd className=\"mt-1 text-text-primary\">{formatEnumLabel(circle.status)}</dd>\n </div>\n <div>\n <dt className=\"text-text-tertiary\">Members</dt>\n <dd className=\"mt-1 text-text-primary\">{formatNumber(circle.memberCount)}</dd>\n </div>\n <div>\n <dt className=\"text-text-tertiary\">Created</dt>\n <dd className=\"mt-1 text-text-primary\">{formatDate(circle.createdAt)}</dd>\n </div>\n </dl>\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Create channel dialog (local — only used here)\n// ---------------------------------------------------------------------------\n\nconst CHANNEL_TYPES: ComChannelType[] = ['TEXT', 'FORUM', 'ANNOUNCEMENT', 'VOICE'];\n\nfunction CreateChannelDialog({\n circleId,\n open,\n onOpenChange,\n onCreated,\n}: {\n circleId: string;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCreated?: (channel: ComChannel) => void;\n}) {\n const createChannel = useComCreateChannel();\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [type, setType] = useState<ComChannelType>('TEXT');\n const [error, setError] = useState<string | null>(null);\n\n const submit = async () => {\n if (!name.trim()) {\n setError('Channel name is required.');\n return;\n }\n try {\n const created = await createChannel.mutateAsync({\n circleId,\n name: name.trim(),\n slug: slugify(name),\n description: description.trim() || null,\n type,\n });\n notify.success('Channel created', `#${created.name}`);\n onOpenChange(false);\n setName('');\n setDescription('');\n setType('TEXT');\n setError(null);\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create a channel</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Channels organise conversations by topic or format.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"flex flex-col gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"channel-name\">Name</Label>\n <Input\n id=\"channel-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"general\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Type</Label>\n <Select value={type} onValueChange={(v) => setType(v as ComChannelType)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CHANNEL_TYPES.map((tp) => (\n <SelectItem key={tp} value={tp}>\n {formatEnumLabel(tp)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"channel-desc\">Description</Label>\n <Textarea\n id=\"channel-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n />\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={createChannel.isPending}>\n Create channel\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Create poll dialog (local — only used here)\n// ---------------------------------------------------------------------------\n\nfunction CreatePollDialog({\n circleId,\n open,\n onOpenChange,\n}: {\n circleId: string;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}) {\n const createPoll = useComCreatePoll();\n const [question, setQuestion] = useState('');\n const [options, setOptions] = useState<string[]>(['', '']);\n const [allowMultiple, setAllowMultiple] = useState(false);\n const [isAnonymous, setIsAnonymous] = useState(false);\n const [closesAt, setClosesAt] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n // Lower bound for the datetime-local picker: \"now\" in the viewer's local zone.\n const minCloses = useMemo(() => {\n const now = new Date();\n now.setMinutes(now.getMinutes() - now.getTimezoneOffset());\n return now.toISOString().slice(0, 16);\n }, []);\n\n const reset = () => {\n setQuestion('');\n setOptions(['', '']);\n setAllowMultiple(false);\n setIsAnonymous(false);\n setClosesAt('');\n setError(null);\n };\n\n const submit = async () => {\n const cleaned = options.map((o) => o.trim()).filter(Boolean);\n if (!question.trim()) {\n setError('A question is required.');\n return;\n }\n if (cleaned.length < 2) {\n setError('Add at least two options.');\n return;\n }\n try {\n await createPoll.mutateAsync({\n circleId,\n question: question.trim(),\n options: cleaned,\n allowMultiple,\n isAnonymous,\n closesAt: closesAt ? new Date(closesAt).toISOString() : null,\n });\n notify.success('Poll created');\n onOpenChange(false);\n reset();\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create a poll</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Gather the circle&apos;s opinion.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"flex flex-col gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"poll-q\">Question</Label>\n <Input\n id=\"poll-q\"\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"What should we build next?\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Options</Label>\n <div className=\"flex flex-col gap-2\">\n {options.map((opt, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <Input\n value={opt}\n onChange={(e) =>\n setOptions((prev) => prev.map((o, idx) => (idx === i ? e.target.value : o)))\n }\n placeholder={`Option ${i + 1}`}\n />\n {options.length > 2 && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setOptions((prev) => prev.filter((_, idx) => idx !== i))}\n aria-label={`Remove option ${i + 1}`}\n >\n ✕\n </Button>\n )}\n </div>\n ))}\n {options.length < 8 && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOptions((prev) => [...prev, ''])}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add option\n </Button>\n )}\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"poll-closes\">Closes at</Label>\n <Input\n id=\"poll-closes\"\n type=\"datetime-local\"\n min={minCloses}\n value={closesAt}\n onChange={(e) => setClosesAt(e.target.value)}\n />\n <p className=\"text-xs text-text-tertiary\">\n Optional — leave empty to keep voting open indefinitely.\n </p>\n </div>\n\n <div className=\"flex flex-col divide-y divide-border-seam rounded-xl border border-border-seam\">\n <div className=\"flex items-center justify-between gap-4 px-3 py-3\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"poll-multi\">Allow multiple choices</Label>\n <p className=\"text-xs text-text-tertiary\">Let voters pick more than one option.</p>\n </div>\n <Switch id=\"poll-multi\" checked={allowMultiple} onCheckedChange={setAllowMultiple} />\n </div>\n <div className=\"flex items-center justify-between gap-4 px-3 py-3\">\n <div className=\"min-w-0\">\n <Label htmlFor=\"poll-anon\">Anonymous voting</Label>\n <p className=\"text-xs text-text-tertiary\">Hide who voted for each option.</p>\n </div>\n <Switch id=\"poll-anon\" checked={isAnonymous} onCheckedChange={setIsAnonymous} />\n </div>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={createPoll.isPending}>\n Create poll\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CirclesPage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/circles/CirclesPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuEH,wBAAgB,WAAW,4CA8H1B"}
1
+ {"version":3,"file":"CirclesPage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/circles/CirclesPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmGH,wBAAgB,WAAW,4CAkI1B"}
@@ -1,6 +1,6 @@
1
1
  import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
2
- import { PageLayout as t } from "../../components/PageLayout.js";
3
2
  import "../../context/index.js";
3
+ import { PageLayout as t } from "../../components/PageLayout.js";
4
4
  import { useComCircles as n, useComMemberships as r } from "../../hooks/useComradeCircleApi.js";
5
5
  import { useCursorPager as i } from "../../hooks/useCursorPager.js";
6
6
  import "../../hooks/index.js";
@@ -17,6 +17,36 @@ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
17
17
  import { Button as v, GlassCard as y, Select as b, SelectContent as x, SelectItem as S, SelectTrigger as C, SelectValue as w, Skeleton as T } from "@burdenoff/fe-libs/ui";
18
18
  //#region src/comradecircle/pages/circles/CirclesPage.tsx
19
19
  function E() {
20
+ return /* @__PURE__ */ _("svg", {
21
+ viewBox: "0 0 64 64",
22
+ className: "h-12 w-12",
23
+ fill: "none",
24
+ stroke: "currentColor",
25
+ strokeWidth: "2",
26
+ "aria-hidden": "true",
27
+ children: [
28
+ /* @__PURE__ */ g("circle", {
29
+ cx: "24",
30
+ cy: "26",
31
+ r: "14",
32
+ opacity: "0.9"
33
+ }),
34
+ /* @__PURE__ */ g("circle", {
35
+ cx: "42",
36
+ cy: "26",
37
+ r: "14",
38
+ opacity: "0.55"
39
+ }),
40
+ /* @__PURE__ */ g("circle", {
41
+ cx: "33",
42
+ cy: "42",
43
+ r: "14",
44
+ opacity: "0.3"
45
+ })
46
+ ]
47
+ });
48
+ }
49
+ function D() {
20
50
  return /* @__PURE__ */ _(y, {
21
51
  treatment: "plain",
22
52
  className: "flex flex-col p-5",
@@ -46,7 +76,7 @@ function E() {
46
76
  ]
47
77
  });
48
78
  }
49
- var D = 12, O = [
79
+ var O = 12, k = [
50
80
  {
51
81
  value: "ALL",
52
82
  label: "All"
@@ -59,7 +89,7 @@ var D = 12, O = [
59
89
  value: "ARCHIVED",
60
90
  label: "Archived"
61
91
  }
62
- ], k = [
92
+ ], A = [
63
93
  {
64
94
  value: "ALL",
65
95
  label: "All visibility"
@@ -77,66 +107,69 @@ var D = 12, O = [
77
107
  label: "Secret"
78
108
  }
79
109
  ];
80
- function A() {
81
- let { navigateTo: y, currentUser: T } = e(), A = i(), [j, M] = p("ALL"), [N, P] = p("ALL"), [F, I] = p(!1), { data: L, isLoading: R, isError: z, refetch: B } = n({
82
- status: j === "ALL" ? void 0 : j,
83
- visibility: N === "ALL" ? void 0 : N,
110
+ function j() {
111
+ let { navigateTo: y, currentUser: T } = e(), j = i(), [M, N] = p("ALL"), [P, F] = p("ALL"), [I, L] = p(!1), { data: R, isLoading: z, isError: B, refetch: V } = n({
112
+ status: M === "ALL" ? void 0 : M,
113
+ visibility: P === "ALL" ? void 0 : P,
84
114
  pagination: {
85
- limit: D,
86
- cursor: A.cursor
115
+ limit: O,
116
+ cursor: j.cursor
87
117
  }
88
- }), { data: V } = r({ status: "ACTIVE" }), H = f(() => {
118
+ }), { data: H } = r({ status: "ACTIVE" }), U = f(() => {
89
119
  let e = /* @__PURE__ */ new Map();
90
- for (let t of V?.items ?? []) t.memberId === T?.id && e.set(t.circleId, t);
120
+ for (let t of H?.items ?? []) t.memberId === T?.id && e.set(t.circleId, t);
91
121
  return e;
92
- }, [V, T?.id]), U = L?.items ?? [];
122
+ }, [H, T?.id]), W = R?.items ?? [];
93
123
  return /* @__PURE__ */ _(t, {
94
124
  title: "Circles",
95
125
  titleGradient: !0,
96
126
  aurora: !0,
97
127
  description: "Discover and join communities — each circle has its own channels, members, events, and polls.",
98
128
  actions: /* @__PURE__ */ _(v, {
99
- onClick: () => I(!0),
129
+ onClick: () => L(!0),
100
130
  children: [/* @__PURE__ */ g(m, { className: "mr-1.5 h-4 w-4" }), " Create circle"]
101
131
  }),
102
132
  children: [
103
133
  /* @__PURE__ */ _("div", {
104
134
  className: "mb-5 flex flex-wrap items-center gap-3",
105
135
  children: [/* @__PURE__ */ g(l, {
106
- options: O,
107
- value: j,
136
+ options: k,
137
+ value: M,
108
138
  onChange: (e) => {
109
- M(e), A.reset();
139
+ N(e), j.reset();
110
140
  }
111
141
  }), /* @__PURE__ */ _(b, {
112
- value: N,
142
+ value: P,
113
143
  onValueChange: (e) => {
114
- P(e), A.reset();
144
+ F(e), j.reset();
115
145
  },
116
146
  children: [/* @__PURE__ */ g(C, {
117
147
  className: "w-[10.5rem]",
118
148
  "aria-label": "Filter by visibility",
119
149
  children: /* @__PURE__ */ g(w, {})
120
- }), /* @__PURE__ */ g(x, { children: k.map((e) => /* @__PURE__ */ g(S, {
150
+ }), /* @__PURE__ */ g(x, { children: A.map((e) => /* @__PURE__ */ g(S, {
121
151
  value: e.value,
122
152
  children: e.label
123
153
  }, e.value)) })]
124
154
  })]
125
155
  }),
126
- R ? /* @__PURE__ */ g("div", {
156
+ z ? /* @__PURE__ */ g("div", {
127
157
  className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
128
158
  "aria-busy": "true",
129
159
  "aria-label": "Loading circles",
130
- children: Array.from({ length: 6 }, (e, t) => /* @__PURE__ */ g(E, {}, t))
131
- }) : z ? /* @__PURE__ */ g(s, {
160
+ children: Array.from({ length: 6 }, (e, t) => /* @__PURE__ */ g(D, {}, t))
161
+ }) : B ? /* @__PURE__ */ g(s, {
132
162
  message: "We couldn't load circles.",
133
- onRetry: () => B()
134
- }) : U.length === 0 && A.page === 0 ? /* @__PURE__ */ g(o, {
135
- illustration: "empty-people",
163
+ onRetry: () => V()
164
+ }) : W.length === 0 && j.page === 0 ? /* @__PURE__ */ g(o, {
165
+ icon: /* @__PURE__ */ g("span", {
166
+ className: "inline-flex h-20 w-20 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-accent",
167
+ children: /* @__PURE__ */ g(E, {})
168
+ }),
136
169
  title: "No circles found",
137
- description: j === "ALL" && N === "ALL" ? "Be the first to start a community. Create a circle to gather members around a shared interest." : "No circles match these filters. Try widening them or start your own.",
170
+ description: M === "ALL" && P === "ALL" ? "Be the first to start a community. Create a circle to gather members around a shared interest." : "No circles match these filters. Try widening them or start your own.",
138
171
  action: /* @__PURE__ */ _(v, {
139
- onClick: () => I(!0),
172
+ onClick: () => L(!0),
140
173
  children: [/* @__PURE__ */ g(m, { className: "mr-1.5 h-4 w-4" }), " Create circle"]
141
174
  })
142
175
  }) : /* @__PURE__ */ _(h, { children: [
@@ -144,36 +177,36 @@ function A() {
144
177
  className: "mb-3 text-sm text-text-tertiary",
145
178
  children: [
146
179
  "Showing ",
147
- a(U.length),
180
+ a(W.length),
148
181
  " circle",
149
- U.length === 1 ? "" : "s"
182
+ W.length === 1 ? "" : "s"
150
183
  ]
151
184
  }),
152
185
  /* @__PURE__ */ g("div", {
153
186
  className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3",
154
- children: U.map((e) => /* @__PURE__ */ g(u, {
187
+ children: W.map((e) => /* @__PURE__ */ g(u, {
155
188
  circle: e,
156
- membership: H.get(e.id) ?? null,
189
+ membership: U.get(e.id) ?? null,
157
190
  onOpen: () => y("circles/" + e.id)
158
191
  }, e.id))
159
192
  }),
160
193
  /* @__PURE__ */ g(c, {
161
- page: A.page,
162
- pageSize: D,
163
- itemCount: U.length,
164
- hasMore: L?.pageInfo.hasMore ?? !1,
165
- onPageChange: (e) => A.onPageChange(e, L?.pageInfo.cursor)
194
+ page: j.page,
195
+ pageSize: O,
196
+ itemCount: W.length,
197
+ hasMore: R?.pageInfo.hasMore ?? !1,
198
+ onPageChange: (e) => j.onPageChange(e, R?.pageInfo.cursor)
166
199
  })
167
200
  ] }),
168
201
  /* @__PURE__ */ g(d, {
169
- open: F,
170
- onOpenChange: I,
202
+ open: I,
203
+ onOpenChange: L,
171
204
  onCreated: (e) => y("circles/" + e.id)
172
205
  })
173
206
  ]
174
207
  });
175
208
  }
176
209
  //#endregion
177
- export { A as CirclesPage };
210
+ export { j as CirclesPage };
178
211
 
179
212
  //# sourceMappingURL=CirclesPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CirclesPage.js","names":[],"sources":["../../../../src/comradecircle/pages/circles/CirclesPage.tsx"],"sourcesContent":["/**\n * CirclesPage — the circle discovery & browse experience.\n * @module comradecircle/pages/circles\n *\n * Reads `comListCircles` (filtered by status + visibility) and, in parallel, the\n * caller's active memberships (`comListMemberships`) so each tile can show the\n * right affordance: Join for circles you don't belong to, Open/Leave + your role\n * for the ones you do. Anyone can spin up a new circle from the header action.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus } from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { CircleCard } from '../../components/CircleCard';\nimport { CreateCircleDialog } from '../../components/CreateCircleDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useComCircles, useComMemberships, useCursorPager } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatNumber } from '../../utils/formatters';\nimport type { ComCircleVisibility, ComMembership } from '../../types';\n\n/** Circle-tile-shaped placeholder used while the directory loads. */\nfunction CircleCardSkeleton() {\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start gap-3\">\n <Skeleton className=\"h-12 w-12 shrink-0 rounded-full\" />\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-2/3 rounded\" />\n <Skeleton className=\"h-3 w-1/3 rounded\" />\n <div className=\"flex gap-1.5 pt-1\">\n <Skeleton className=\"h-4 w-14 rounded-full\" />\n <Skeleton className=\"h-4 w-16 rounded-full\" />\n </div>\n </div>\n </div>\n <div className=\"mt-3 space-y-2\">\n <Skeleton className=\"h-3 w-full rounded\" />\n <Skeleton className=\"h-3 w-4/5 rounded\" />\n </div>\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <Skeleton className=\"h-4 w-24 rounded\" />\n <Skeleton className=\"h-8 w-16 rounded-lg\" />\n </div>\n </GlassCard>\n );\n}\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = 'ALL' | 'ACTIVE' | 'ARCHIVED';\ntype VisibilityFilter = 'ALL' | ComCircleVisibility;\n\nconst STATUS_OPTIONS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst VISIBILITY_OPTIONS: { value: VisibilityFilter; label: string }[] = [\n { value: 'ALL', label: 'All visibility' },\n { value: 'PUBLIC', label: 'Public' },\n { value: 'PRIVATE', label: 'Private' },\n { value: 'SECRET', label: 'Secret' },\n];\n\nexport function CirclesPage() {\n const { navigateTo, currentUser } = useComradeCircle();\n const pager = useCursorPager();\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [visibility, setVisibility] = useState<VisibilityFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const { data, isLoading, isError, refetch } = useComCircles({\n status: status === 'ALL' ? undefined : status,\n visibility: visibility === 'ALL' ? undefined : visibility,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n // Parallel: the caller's active memberships, keyed by circle so each tile knows\n // whether the current user already belongs (and in what role).\n const { data: membershipData } = useComMemberships({ status: 'ACTIVE' });\n\n const membershipByCircle = useMemo(() => {\n const map = new Map<string, ComMembership>();\n for (const membership of membershipData?.items ?? []) {\n if (membership.memberId === currentUser?.id) {\n map.set(membership.circleId, membership);\n }\n }\n return map;\n }, [membershipData, currentUser?.id]);\n\n const circles = data?.items ?? [];\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const changeVisibility = (next: string) => {\n setVisibility(next as VisibilityFilter);\n pager.reset();\n };\n\n return (\n <PageLayout\n title=\"Circles\"\n titleGradient\n aurora\n description=\"Discover and join communities — each circle has its own channels, members, events, and polls.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Create circle\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs options={STATUS_OPTIONS} value={status} onChange={changeStatus} />\n <Select value={visibility} onValueChange={changeVisibility}>\n <SelectTrigger className=\"w-[10.5rem]\" aria-label=\"Filter by visibility\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITY_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <div\n className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\"\n aria-busy=\"true\"\n aria-label=\"Loading circles\"\n >\n {Array.from({ length: 6 }, (_, i) => (\n <CircleCardSkeleton key={i} />\n ))}\n </div>\n ) : isError ? (\n <ErrorView message=\"We couldn't load circles.\" onRetry={() => refetch()} />\n ) : circles.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No circles found\"\n description={\n status === 'ALL' && visibility === 'ALL'\n ? 'Be the first to start a community. Create a circle to gather members around a shared interest.'\n : 'No circles match these filters. Try widening them or start your own.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Create circle\n </Button>\n }\n />\n ) : (\n <>\n <p className=\"mb-3 text-sm text-text-tertiary\">\n Showing {formatNumber(circles.length)} circle{circles.length === 1 ? '' : 's'}\n </p>\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {circles.map((circle) => (\n <CircleCard\n key={circle.id}\n circle={circle}\n membership={membershipByCircle.get(circle.id) ?? null}\n onOpen={() => navigateTo('circles/' + circle.id)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={circles.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </>\n )}\n\n <CreateCircleDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n onCreated={(circle) => navigateTo('circles/' + circle.id)}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CirclesPage.js","names":[],"sources":["../../../../src/comradecircle/pages/circles/CirclesPage.tsx"],"sourcesContent":["/**\n * CirclesPage — the circle discovery & browse experience.\n * @module comradecircle/pages/circles\n *\n * Reads `comListCircles` (filtered by status + visibility) and, in parallel, the\n * caller's active memberships (`comListMemberships`) so each tile can show the\n * right affordance: Join for circles you don't belong to, Open/Leave + your role\n * for the ones you do. Anyone can spin up a new circle from the header action.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus } from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { CircleCard } from '../../components/CircleCard';\nimport { CreateCircleDialog } from '../../components/CreateCircleDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useComCircles, useComMemberships, useCursorPager } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatNumber } from '../../utils/formatters';\nimport type { ComCircleVisibility, ComMembership } from '../../types';\n\n/**\n * ConnectedCirclesIcon — product-specific zero-state artwork (BOFF-3973).\n *\n * `comradecircle-23-circles-empty-state.png` pairs \"No circles found\" with a\n * custom connected-circles motif rather than the generic `empty-people`\n * illustration from `fe-libs/ui`. Composed inline (per the ticket's own\n * suggested implementation) instead of adding a new shared illustration\n * asset, since `fe-libs/ui` is consumed fleet-wide and a product-specific\n * glyph doesn't belong there. Uses `currentColor` only — no raw color\n * literals — so it inherits the semantic accent token from its wrapper.\n */\nfunction ConnectedCirclesIcon() {\n return (\n <svg\n viewBox=\"0 0 64 64\"\n className=\"h-12 w-12\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n aria-hidden=\"true\"\n >\n <circle cx=\"24\" cy=\"26\" r=\"14\" opacity=\"0.9\" />\n <circle cx=\"42\" cy=\"26\" r=\"14\" opacity=\"0.55\" />\n <circle cx=\"33\" cy=\"42\" r=\"14\" opacity=\"0.3\" />\n </svg>\n );\n}\n\n/** Circle-tile-shaped placeholder used while the directory loads. */\nfunction CircleCardSkeleton() {\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start gap-3\">\n <Skeleton className=\"h-12 w-12 shrink-0 rounded-full\" />\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-2/3 rounded\" />\n <Skeleton className=\"h-3 w-1/3 rounded\" />\n <div className=\"flex gap-1.5 pt-1\">\n <Skeleton className=\"h-4 w-14 rounded-full\" />\n <Skeleton className=\"h-4 w-16 rounded-full\" />\n </div>\n </div>\n </div>\n <div className=\"mt-3 space-y-2\">\n <Skeleton className=\"h-3 w-full rounded\" />\n <Skeleton className=\"h-3 w-4/5 rounded\" />\n </div>\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <Skeleton className=\"h-4 w-24 rounded\" />\n <Skeleton className=\"h-8 w-16 rounded-lg\" />\n </div>\n </GlassCard>\n );\n}\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = 'ALL' | 'ACTIVE' | 'ARCHIVED';\ntype VisibilityFilter = 'ALL' | ComCircleVisibility;\n\nconst STATUS_OPTIONS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst VISIBILITY_OPTIONS: { value: VisibilityFilter; label: string }[] = [\n { value: 'ALL', label: 'All visibility' },\n { value: 'PUBLIC', label: 'Public' },\n { value: 'PRIVATE', label: 'Private' },\n { value: 'SECRET', label: 'Secret' },\n];\n\nexport function CirclesPage() {\n const { navigateTo, currentUser } = useComradeCircle();\n const pager = useCursorPager();\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [visibility, setVisibility] = useState<VisibilityFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const { data, isLoading, isError, refetch } = useComCircles({\n status: status === 'ALL' ? undefined : status,\n visibility: visibility === 'ALL' ? undefined : visibility,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n // Parallel: the caller's active memberships, keyed by circle so each tile knows\n // whether the current user already belongs (and in what role).\n const { data: membershipData } = useComMemberships({ status: 'ACTIVE' });\n\n const membershipByCircle = useMemo(() => {\n const map = new Map<string, ComMembership>();\n for (const membership of membershipData?.items ?? []) {\n if (membership.memberId === currentUser?.id) {\n map.set(membership.circleId, membership);\n }\n }\n return map;\n }, [membershipData, currentUser?.id]);\n\n const circles = data?.items ?? [];\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const changeVisibility = (next: string) => {\n setVisibility(next as VisibilityFilter);\n pager.reset();\n };\n\n return (\n <PageLayout\n title=\"Circles\"\n titleGradient\n aurora\n description=\"Discover and join communities — each circle has its own channels, members, events, and polls.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Create circle\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs options={STATUS_OPTIONS} value={status} onChange={changeStatus} />\n <Select value={visibility} onValueChange={changeVisibility}>\n <SelectTrigger className=\"w-[10.5rem]\" aria-label=\"Filter by visibility\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITY_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <div\n className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\"\n aria-busy=\"true\"\n aria-label=\"Loading circles\"\n >\n {Array.from({ length: 6 }, (_, i) => (\n <CircleCardSkeleton key={i} />\n ))}\n </div>\n ) : isError ? (\n <ErrorView message=\"We couldn't load circles.\" onRetry={() => refetch()} />\n ) : circles.length === 0 && pager.page === 0 ? (\n <EmptyState\n icon={\n <span className=\"inline-flex h-20 w-20 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-accent\">\n <ConnectedCirclesIcon />\n </span>\n }\n title=\"No circles found\"\n description={\n status === 'ALL' && visibility === 'ALL'\n ? 'Be the first to start a community. Create a circle to gather members around a shared interest.'\n : 'No circles match these filters. Try widening them or start your own.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Create circle\n </Button>\n }\n />\n ) : (\n <>\n <p className=\"mb-3 text-sm text-text-tertiary\">\n Showing {formatNumber(circles.length)} circle{circles.length === 1 ? '' : 's'}\n </p>\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {circles.map((circle) => (\n <CircleCard\n key={circle.id}\n circle={circle}\n membership={membershipByCircle.get(circle.id) ?? null}\n onOpen={() => navigateTo('circles/' + circle.id)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={circles.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </>\n )}\n\n <CreateCircleDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n onCreated={(circle) => navigateTo('circles/' + circle.id)}\n />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6CA,SAAS,IAAuB;CAC9B,OACE,kBAAC,OAAD;EACE,SAAQ;EACR,WAAU;EACV,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAY;YANd;GAQE,kBAAC,UAAD;IAAQ,IAAG;IAAK,IAAG;IAAK,GAAE;IAAK,SAAQ;GAAO,CAAA;GAC9C,kBAAC,UAAD;IAAQ,IAAG;IAAK,IAAG;IAAK,GAAE;IAAK,SAAQ;GAAQ,CAAA;GAC/C,kBAAC,UAAD;IAAQ,IAAG;IAAK,IAAG;IAAK,GAAE;IAAK,SAAQ;GAAO,CAAA;EAC3C;;AAET;AAGA,SAAS,IAAqB;CAC5B,OACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAU,WAAU,kCAAmC,CAAA,GACvD,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAU,WAAU,oBAAqB,CAAA;MACzC,kBAAC,GAAD,EAAU,WAAU,oBAAqB,CAAA;MACzC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA,GAC7C,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA,CAC1C;;KACF;MACF;;GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAU,WAAU,qBAAsB,CAAA,GAC1C,kBAAC,GAAD,EAAU,WAAU,oBAAqB,CAAA,CACtC;;GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAU,WAAU,mBAAoB,CAAA,GACxC,kBAAC,GAAD,EAAU,WAAU,sBAAuB,CAAA,CACxC;;EACI;;AAEf;AAEA,IAAM,IAAY,IAKZ,IAA+C;CACnD;EAAE,OAAO;EAAO,OAAO;CAAM;CAC7B;EAAE,OAAO;EAAU,OAAO;CAAS;CACnC;EAAE,OAAO;EAAY,OAAO;CAAW;AACzC,GAEM,IAAmE;CACvE;EAAE,OAAO;EAAO,OAAO;CAAiB;CACxC;EAAE,OAAO;EAAU,OAAO;CAAS;CACnC;EAAE,OAAO;EAAW,OAAO;CAAU;CACrC;EAAE,OAAO;EAAU,OAAO;CAAS;AACrC;AAEA,SAAgB,IAAc;CAC5B,IAAM,EAAE,eAAY,mBAAgB,EAAiB,GAC/C,IAAQ,EAAe,GACvB,CAAC,GAAQ,KAAa,EAAuB,KAAK,GAClD,CAAC,GAAY,KAAiB,EAA2B,KAAK,GAC9D,CAAC,GAAY,KAAiB,EAAS,EAAK,GAE5C,EAAE,SAAM,cAAW,YAAS,eAAY,EAAc;EAC1D,QAAQ,MAAW,QAAQ,KAAA,IAAY;EACvC,YAAY,MAAe,QAAQ,KAAA,IAAY;EAC/C,YAAY;GAAE,OAAO;GAAW,QAAQ,EAAM;EAAO;CACvD,CAAC,GAIK,EAAE,MAAM,MAAmB,EAAkB,EAAE,QAAQ,SAAS,CAAC,GAEjE,IAAqB,QAAc;EACvC,IAAM,oBAAM,IAAI,IAA2B;EAC3C,KAAK,IAAM,KAAc,GAAgB,SAAS,CAAC,GACjD,AAAI,EAAW,aAAa,GAAa,MACvC,EAAI,IAAI,EAAW,UAAU,CAAU;EAG3C,OAAO;CACT,GAAG,CAAC,GAAgB,GAAa,EAAE,CAAC,GAE9B,IAAU,GAAM,SAAS,CAAC;CAYhC,OACE,kBAAC,GAAD;EACE,OAAM;EACN,eAAA;EACA,QAAA;EACA,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,eAAe,EAAc,EAAI;aAAzC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,gBAC7B;;YARZ;GAWE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAY,SAAS;KAAgB,OAAO;KAAQ,WAvBpC,MAAuB;MAE3C,AADA,EAAU,CAAI,GACd,EAAM,MAAM;KACd;IAoBmF,CAAA,GAC7E,kBAAC,GAAD;KAAQ,OAAO;KAAY,gBAnBP,MAAiB;MAEzC,AADA,EAAc,CAAwB,GACtC,EAAM,MAAM;KACd;eAgBM,CACE,kBAAC,GAAD;MAAe,WAAU;MAAc,cAAW;gBAChD,kBAAC,GAAD,CAAc,CAAA;KACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAmB,KAAK,MACvB,kBAAC,GAAD;MAA4B,OAAO,EAAI;gBACpC,EAAI;KACK,GAFK,EAAI,KAET,CACb,EACY,CAAA,CACT;MACL;;GAEJ,IACC,kBAAC,OAAD;IACE,WAAU;IACV,aAAU;IACV,cAAW;cAEV,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAC7B,kBAAC,GAAD,CAA6B,GAAJ,CAAI,CAC9B;GACE,CAAA,IACH,IACF,kBAAC,GAAD;IAAW,SAAQ;IAA4B,eAAe,EAAQ;GAAI,CAAA,IACxE,EAAQ,WAAW,KAAK,EAAM,SAAS,IACzC,kBAAC,GAAD;IACE,MACE,kBAAC,QAAD;KAAM,WAAU;eACd,kBAAC,GAAD,CAAuB,CAAA;IACnB,CAAA;IAER,OAAM;IACN,aACE,MAAW,SAAS,MAAe,QAC/B,mGACA;IAEN,QACE,kBAAC,GAAD;KAAQ,eAAe,EAAc,EAAI;eAAzC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,gBAC7B;;GAEX,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA+C;MACpC,EAAa,EAAQ,MAAM;MAAE;MAAQ,EAAQ,WAAW,IAAI,KAAK;KACzE;;IACH,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAQ,KAAK,MACZ,kBAAC,GAAD;MAEU;MACR,YAAY,EAAmB,IAAI,EAAO,EAAE,KAAK;MACjD,cAAc,EAAW,aAAa,EAAO,EAAE;KAChD,GAJM,EAAO,EAIb,CACF;IACE,CAAA;IACL,kBAAC,GAAD;KACE,MAAM,EAAM;KACZ,UAAU;KACV,WAAW,EAAQ;KACnB,SAAS,GAAM,SAAS,WAAW;KACnC,eAAe,MAAW,EAAM,aAAa,GAAQ,GAAM,SAAS,MAAM;IAC3E,CAAA;GACD,EAAA,CAAA;GAGJ,kBAAC,GAAD;IACE,MAAM;IACN,cAAc;IACd,YAAY,MAAW,EAAW,aAAa,EAAO,EAAE;GACzD,CAAA;EACS;;AAEhB"}
@@ -1,7 +1,7 @@
1
1
  import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
2
+ import "../../context/index.js";
2
3
  import { cn as t } from "../../utils/cn.js";
3
4
  import { PageLayout as n } from "../../components/PageLayout.js";
4
- import "../../context/index.js";
5
5
  import { useComEvent as ee, useComRsvpEvent as te, useComTransitionEvent as r } from "../../hooks/useComradeCircleApi.js";
6
6
  import "../../hooks/index.js";
7
7
  import { MemberIdentity as i } from "../../components/MemberIdentity.js";
@@ -1,6 +1,6 @@
1
1
  import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
2
- import { PageLayout as t } from "../../components/PageLayout.js";
3
2
  import "../../context/index.js";
3
+ import { PageLayout as t } from "../../components/PageLayout.js";
4
4
  import { useComEvents as n } from "../../hooks/useComradeCircleApi.js";
5
5
  import { useCursorPager as r } from "../../hooks/useCursorPager.js";
6
6
  import "../../hooks/index.js";
@@ -1,7 +1,7 @@
1
1
  import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
2
+ import "../../context/index.js";
2
3
  import { cn as t } from "../../utils/cn.js";
3
4
  import { PageLayout as n } from "../../components/PageLayout.js";
4
- import "../../context/index.js";
5
5
  import { useComFollow as r, useComMyFollows as i, useComProfile as a, useComUnfollow as o } from "../../hooks/useComradeCircleApi.js";
6
6
  import "../../hooks/index.js";
7
7
  import { Avatar as s } from "../../components/Avatar.js";
@@ -1,6 +1,6 @@
1
1
  import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
2
- import { PageLayout as t } from "../../components/PageLayout.js";
3
2
  import "../../context/index.js";
3
+ import { PageLayout as t } from "../../components/PageLayout.js";
4
4
  import { useComFollow as n, useComMyFollows as r, useComProfiles as i, useComUnfollow as a } from "../../hooks/useComradeCircleApi.js";
5
5
  import { useCursorPager as o } from "../../hooks/useCursorPager.js";
6
6
  import "../../hooks/index.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * PostThreadPage — a dedicated, focused view of a single post and its thread.
3
+ * @module comradecircle/pages/posts
4
+ *
5
+ * BOFF-3955: the mocks (`comradecircle-14-channel-post-thread.png`) show a post
6
+ * opened into its own surface — author, body, reactions, and comments — rather
7
+ * than only the inline collapsible thread inside the channel feed. Reads
8
+ * `comPost(id)` (post + its comments in one round trip) and renders the
9
+ * existing {@link PostCard} (which already composes {@link ReactionBar} +
10
+ * {@link CommentThread} when expanded) so the thread view stays in lock-step
11
+ * with the inline one instead of duplicating rendering logic.
12
+ */
13
+ export declare function PostThreadPage(): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=PostThreadPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostThreadPage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/posts/PostThreadPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,wBAAgB,cAAc,4CA2C7B"}