@burdenoff/microfe-comradecircle 2026.716.1 → 2026.720.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/comradecircle/ComradeCircleRoot.js.map +1 -1
  2. package/dist/comradecircle/ComradeCircleRoutes.js.map +1 -1
  3. package/dist/comradecircle/components/Avatar.js.map +1 -1
  4. package/dist/comradecircle/components/AvatarUploadField.js.map +1 -1
  5. package/dist/comradecircle/components/CircleCard.js.map +1 -1
  6. package/dist/comradecircle/components/CommentThread.js.map +1 -1
  7. package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -1
  8. package/dist/comradecircle/components/CreateEventDialog.js.map +1 -1
  9. package/dist/comradecircle/components/EmptyState.js.map +1 -1
  10. package/dist/comradecircle/components/EventCard.js.map +1 -1
  11. package/dist/comradecircle/components/FileImage.js.map +1 -1
  12. package/dist/comradecircle/components/FilterTabs.js.map +1 -1
  13. package/dist/comradecircle/components/ImageAttachments.js.map +1 -1
  14. package/dist/comradecircle/components/MemberIdentity.js.map +1 -1
  15. package/dist/comradecircle/components/MiniBar.js.map +1 -1
  16. package/dist/comradecircle/components/PageLayout.js.map +1 -1
  17. package/dist/comradecircle/components/Pagination.js.map +1 -1
  18. package/dist/comradecircle/components/PollCard.js.map +1 -1
  19. package/dist/comradecircle/components/PostCard.js.map +1 -1
  20. package/dist/comradecircle/components/PostComposer.js.map +1 -1
  21. package/dist/comradecircle/components/ReactionBar.js.map +1 -1
  22. package/dist/comradecircle/components/StateViews.js.map +1 -1
  23. package/dist/comradecircle/components/StatusBadge.js.map +1 -1
  24. package/dist/comradecircle/components/Tabs.js.map +1 -1
  25. package/dist/comradecircle/components/TagInput.js.map +1 -1
  26. package/dist/comradecircle/context/ComradeCircleContext.js.map +1 -1
  27. package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
  28. package/dist/comradecircle/hooks/useCursorPager.js.map +1 -1
  29. package/dist/comradecircle/hooks/useFiles.js.map +1 -1
  30. package/dist/comradecircle/lib/graphql-cache.js.map +1 -1
  31. package/dist/comradecircle/lib/toast.js.map +1 -1
  32. package/dist/comradecircle/pages/FeedPage.js.map +1 -1
  33. package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
  34. package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
  35. package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
  36. package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
  37. package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
  38. package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
  39. package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
  40. package/dist/comradecircle/utils/cn.js.map +1 -1
  41. package/dist/comradecircle/utils/formatters.js.map +1 -1
  42. package/dist/comradecircle/utils/navigation.js.map +1 -1
  43. package/dist/comradecircle/utils/slug.js.map +1 -1
  44. package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
  45. package/dist/node_modules/sonner/dist/index.js.map +1 -1
  46. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
  47. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
  48. package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -1
  49. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  50. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"useFiles.js","names":[],"sources":["../../../src/comradecircle/hooks/useFiles.ts"],"sourcesContent":["/**\n * File hooks — attachments & avatars via wspace-files-svc (NO duplication).\n * @module comradecircle/hooks\n *\n * ComradeCircle never stores blobs. Posts keep `attachmentFileIds` and circles /\n * profiles keep `avatarFileId` — all pointing at files owned by wspace-files-svc.\n * Upload goes through the shared `uploadBlobToFiles` storage helper (presigned\n * 3-step upload) against the workspace Apollo client the shell initialises;\n * display resolves a file id to a presigned URL via `getFileDownloadUrl`.\n */\n\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch, getWorkspaceClient, gql } from '@burdenoff/fe-libs/shared/graphql';\nimport { uploadBlobToFiles } from '@burdenoff/fe-libs/storage';\n\nimport { useComradeCircle } from '../context';\n\n/** One year — avatars/attachments are long-lived references, not ephemeral links. */\nconst DOWNLOAD_URL_TTL_SECONDS = 31_536_000;\n\nexport const FILE_FOLDERS = {\n avatars: '/ComradeCircle/Avatars',\n posts: '/ComradeCircle/Posts',\n} as const;\n\nconst ENSURE_FOLDER = gql`\n mutation ComEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) {\n id\n }\n }\n`;\n\nconst GET_FILE_DOWNLOAD_URL = gql`\n query ComGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\nexport interface UploadedImage {\n id: string;\n url: string;\n name: string;\n}\n\ninterface EnsureFolderData {\n ensureFolderByPath?: { id: string } | null;\n}\ninterface DownloadUrlData {\n getFileDownloadUrl?: string | null;\n}\n\n/**\n * Upload an image to wspace-files-svc and return its id + a presigned URL.\n * The caller persists the id (attachmentFileIds / avatarFileId) via the entity's\n * own comradecircle mutation; the URL is used for an immediate preview.\n */\nexport async function uploadImageToFiles(file: File, folderPath: string): Promise<UploadedImage> {\n const client = getWorkspaceClient();\n\n const folderResult = await client.mutate<EnsureFolderData>({\n mutation: ENSURE_FOLDER,\n variables: { path: folderPath },\n });\n const folderId = folderResult.data?.ensureFolderByPath?.id;\n if (!folderId) {\n throw new Error('Could not resolve the upload folder.');\n }\n\n const uploaded = await uploadBlobToFiles({\n client,\n blob: file,\n filename: file.name,\n mimeType: file.type || 'application/octet-stream',\n folderId,\n });\n\n const urlResult = await client.query<DownloadUrlData>({\n query: GET_FILE_DOWNLOAD_URL,\n variables: { id: uploaded.id, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n fetchPolicy: 'no-cache',\n });\n const url = urlResult.data?.getFileDownloadUrl;\n if (!url) {\n throw new Error('The file was uploaded but no download URL was returned.');\n }\n\n return { id: uploaded.id, url, name: uploaded.name };\n}\n\n/** Resolve a wspace-files-svc file id to a presigned download URL for display. */\nexport function useFileUrl(fileId?: string | null) {\n const { apiGatewayUrl } = useComradeCircle();\n return useQuery({\n queryKey: ['comradecircle', 'file-url', fileId ?? ''],\n enabled: Boolean(fileId),\n staleTime: 30 * 60 * 1000,\n retry: 1,\n queryFn: async () => {\n const response = await graphqlFetch<{ getFileDownloadUrl: string | null }>({\n gateway: 'workspace',\n baseUrl: apiGatewayUrl,\n workspaceToken: true,\n query: `query ComFileUrl($id: ID!, $expiresIn: Int) { getFileDownloadUrl(id: $id, expiresIn: $expiresIn) }`,\n variables: { id: fileId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Could not resolve file URL');\n }\n return response.data?.getFileDownloadUrl ?? null;\n },\n });\n}\n"],"mappings":";;;;;;AAkBA,IAAM,IAA2B,SAEpB,IAAe;CAC1B,SAAS;CACT,OAAO;CACR,EAEK,IAAgB,CAAG;;;;;;GAQnB,IAAwB,CAAG;;;;;AAwBjC,eAAsB,EAAmB,GAAY,GAA4C;CAC/F,IAAM,IAAS,GAAoB,EAM7B,KAJe,MAAM,EAAO,OAAyB;EACzD,UAAU;EACV,WAAW,EAAE,MAAM,GAAY;EAChC,CAAC,EAC4B,MAAM,oBAAoB;AACxD,KAAI,CAAC,EACH,OAAU,MAAM,uCAAuC;CAGzD,IAAM,IAAW,MAAM,EAAkB;EACvC;EACA,MAAM;EACN,UAAU,EAAK;EACf,UAAU,EAAK,QAAQ;EACvB;EACD,CAAC,EAOI,KALY,MAAM,EAAO,MAAuB;EACpD,OAAO;EACP,WAAW;GAAE,IAAI,EAAS;GAAI,WAAW;GAA0B;EACnE,aAAa;EACd,CAAC,EACoB,MAAM;AAC5B,KAAI,CAAC,EACH,OAAU,MAAM,0DAA0D;AAG5E,QAAO;EAAE,IAAI,EAAS;EAAI;EAAK,MAAM,EAAS;EAAM;;AAItD,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,qBAAkB,GAAkB;AAC5C,QAAO,EAAS;EACd,UAAU;GAAC;GAAiB;GAAY,KAAU;GAAG;EACrD,SAAS,EAAQ;EACjB,WAAW,OAAU;EACrB,OAAO;EACP,SAAS,YAAY;GACnB,IAAM,IAAW,MAAM,EAAoD;IACzE,SAAS;IACT,SAAS;IACT,gBAAgB;IAChB,OAAO;IACP,WAAW;KAAE,IAAI;KAAQ,WAAW;KAA0B;IAC/D,CAAC;AACF,OAAI,EAAS,QAAQ,OACnB,OAAU,MAAM,EAAS,OAAO,IAAI,WAAW,6BAA6B;AAE9E,UAAO,EAAS,MAAM,sBAAsB;;EAE/C,CAAC"}
1
+ {"version":3,"file":"useFiles.js","names":[],"sources":["../../../src/comradecircle/hooks/useFiles.ts"],"sourcesContent":["/**\n * File hooks — attachments & avatars via wspace-files-svc (NO duplication).\n * @module comradecircle/hooks\n *\n * ComradeCircle never stores blobs. Posts keep `attachmentFileIds` and circles /\n * profiles keep `avatarFileId` — all pointing at files owned by wspace-files-svc.\n * Upload goes through the shared `uploadBlobToFiles` storage helper (presigned\n * 3-step upload) against the workspace Apollo client the shell initialises;\n * display resolves a file id to a presigned URL via `getFileDownloadUrl`.\n */\n\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch, getWorkspaceClient, gql } from '@burdenoff/fe-libs/shared/graphql';\nimport { uploadBlobToFiles } from '@burdenoff/fe-libs/storage';\n\nimport { useComradeCircle } from '../context';\n\n/** One year — avatars/attachments are long-lived references, not ephemeral links. */\nconst DOWNLOAD_URL_TTL_SECONDS = 31_536_000;\n\nexport const FILE_FOLDERS = {\n avatars: '/ComradeCircle/Avatars',\n posts: '/ComradeCircle/Posts',\n} as const;\n\nconst ENSURE_FOLDER = gql`\n mutation ComEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) {\n id\n }\n }\n`;\n\nconst GET_FILE_DOWNLOAD_URL = gql`\n query ComGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\nexport interface UploadedImage {\n id: string;\n url: string;\n name: string;\n}\n\ninterface EnsureFolderData {\n ensureFolderByPath?: { id: string } | null;\n}\ninterface DownloadUrlData {\n getFileDownloadUrl?: string | null;\n}\n\n/**\n * Upload an image to wspace-files-svc and return its id + a presigned URL.\n * The caller persists the id (attachmentFileIds / avatarFileId) via the entity's\n * own comradecircle mutation; the URL is used for an immediate preview.\n */\nexport async function uploadImageToFiles(file: File, folderPath: string): Promise<UploadedImage> {\n const client = getWorkspaceClient();\n\n const folderResult = await client.mutate<EnsureFolderData>({\n mutation: ENSURE_FOLDER,\n variables: { path: folderPath },\n });\n const folderId = folderResult.data?.ensureFolderByPath?.id;\n if (!folderId) {\n throw new Error('Could not resolve the upload folder.');\n }\n\n const uploaded = await uploadBlobToFiles({\n client,\n blob: file,\n filename: file.name,\n mimeType: file.type || 'application/octet-stream',\n folderId,\n });\n\n const urlResult = await client.query<DownloadUrlData>({\n query: GET_FILE_DOWNLOAD_URL,\n variables: { id: uploaded.id, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n fetchPolicy: 'no-cache',\n });\n const url = urlResult.data?.getFileDownloadUrl;\n if (!url) {\n throw new Error('The file was uploaded but no download URL was returned.');\n }\n\n return { id: uploaded.id, url, name: uploaded.name };\n}\n\n/** Resolve a wspace-files-svc file id to a presigned download URL for display. */\nexport function useFileUrl(fileId?: string | null) {\n const { apiGatewayUrl } = useComradeCircle();\n return useQuery({\n queryKey: ['comradecircle', 'file-url', fileId ?? ''],\n enabled: Boolean(fileId),\n staleTime: 30 * 60 * 1000,\n retry: 1,\n queryFn: async () => {\n const response = await graphqlFetch<{ getFileDownloadUrl: string | null }>({\n gateway: 'workspace',\n baseUrl: apiGatewayUrl,\n workspaceToken: true,\n query: `query ComFileUrl($id: ID!, $expiresIn: Int) { getFileDownloadUrl(id: $id, expiresIn: $expiresIn) }`,\n variables: { id: fileId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Could not resolve file URL');\n }\n return response.data?.getFileDownloadUrl ?? null;\n },\n });\n}\n"],"mappings":";;;;;;AAkBA,IAAM,IAA2B,SAEpB,IAAe;CAC1B,SAAS;CACT,OAAO;AACT,GAEM,IAAgB,CAAG;;;;;;GAQnB,IAAwB,CAAG;;;;;AAwBjC,eAAsB,EAAmB,GAAY,GAA4C;CAC/F,IAAM,IAAS,EAAmB,GAM5B,KAAW,MAJU,EAAO,OAAyB;EACzD,UAAU;EACV,WAAW,EAAE,MAAM,EAAW;CAChC,CAAC,GAC6B,MAAM,oBAAoB;CACxD,IAAI,CAAC,GACH,MAAU,MAAM,sCAAsC;CAGxD,IAAM,IAAW,MAAM,EAAkB;EACvC;EACA,MAAM;EACN,UAAU,EAAK;EACf,UAAU,EAAK,QAAQ;EACvB;CACF,CAAC,GAOK,KAAM,MALY,EAAO,MAAuB;EACpD,OAAO;EACP,WAAW;GAAE,IAAI,EAAS;GAAI,WAAW;EAAyB;EAClE,aAAa;CACf,CAAC,GACqB,MAAM;CAC5B,IAAI,CAAC,GACH,MAAU,MAAM,yDAAyD;CAG3E,OAAO;EAAE,IAAI,EAAS;EAAI;EAAK,MAAM,EAAS;CAAK;AACrD;AAGA,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,qBAAkB,EAAiB;CAC3C,OAAO,EAAS;EACd,UAAU;GAAC;GAAiB;GAAY,KAAU;EAAE;EACpD,SAAS,EAAQ;EACjB,WAAW,OAAU;EACrB,OAAO;EACP,SAAS,YAAY;GACnB,IAAM,IAAW,MAAM,EAAoD;IACzE,SAAS;IACT,SAAS;IACT,gBAAgB;IAChB,OAAO;IACP,WAAW;KAAE,IAAI;KAAQ,WAAW;IAAyB;GAC/D,CAAC;GACD,IAAI,EAAS,QAAQ,QACnB,MAAU,MAAM,EAAS,OAAO,IAAI,WAAW,4BAA4B;GAE7E,OAAO,EAAS,MAAM,sBAAsB;EAC9C;CACF,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"graphql-cache.js","names":[],"sources":["../../../src/comradecircle/lib/graphql-cache.ts"],"sourcesContent":["/**\n * graphql-cache — invalidate fe-libs' imperative-fetch response cache after writes.\n * @module comradecircle/lib\n *\n * `graphqlFetch` (from `@burdenoff/fe-libs/shared/graphql`) coalesces reads by\n * caching every RESOLVED `query` response for ~15s (keyed by operationName +\n * variables) and de-duping in-flight requests. A refetch issued right after a\n * mutation — which is exactly what our React-Query `invalidateQueries` triggers\n * — otherwise gets served the PRE-mutation response, so created/updated/deleted\n * rows stay invisible until a full page reload.\n *\n * fe-libs' own Apollo client fixes this by calling `clearGraphqlFetchResponseCache()`\n * on every mutation, but that helper is not re-exported from the package's public\n * entry, and we use the raw `graphqlFetch` transport (no Apollo client). The two\n * caches are plain `Map`s that fe-libs stores directly on `globalThis`\n * (`getGraphqlFetchGlobalState()` returns `globalThis`), so we clear the same two\n * globals here — mirroring `clearGraphqlFetchResponseCache` — to force every\n * post-write read back onto the network.\n */\n\ninterface GraphqlFetchGlobals {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n}\n\n/**\n * Clear fe-libs' resolved-response cache and in-flight de-dupe map so the next\n * read of any query hits the gateway. Safe to call unconditionally; a no-op\n * before the caches have been created.\n */\nexport function clearGraphqlResponseCache(): void {\n const g = globalThis as unknown as GraphqlFetchGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n}\n"],"mappings":";AA8BA,SAAgB,IAAkC;CAChD,IAAM,IAAI;AAEV,CADA,EAAE,6BAA6B,OAAO,EACtC,EAAE,gCAAgC,OAAO"}
1
+ {"version":3,"file":"graphql-cache.js","names":[],"sources":["../../../src/comradecircle/lib/graphql-cache.ts"],"sourcesContent":["/**\n * graphql-cache — invalidate fe-libs' imperative-fetch response cache after writes.\n * @module comradecircle/lib\n *\n * `graphqlFetch` (from `@burdenoff/fe-libs/shared/graphql`) coalesces reads by\n * caching every RESOLVED `query` response for ~15s (keyed by operationName +\n * variables) and de-duping in-flight requests. A refetch issued right after a\n * mutation — which is exactly what our React-Query `invalidateQueries` triggers\n * — otherwise gets served the PRE-mutation response, so created/updated/deleted\n * rows stay invisible until a full page reload.\n *\n * fe-libs' own Apollo client fixes this by calling `clearGraphqlFetchResponseCache()`\n * on every mutation, but that helper is not re-exported from the package's public\n * entry, and we use the raw `graphqlFetch` transport (no Apollo client). The two\n * caches are plain `Map`s that fe-libs stores directly on `globalThis`\n * (`getGraphqlFetchGlobalState()` returns `globalThis`), so we clear the same two\n * globals here — mirroring `clearGraphqlFetchResponseCache` — to force every\n * post-write read back onto the network.\n */\n\ninterface GraphqlFetchGlobals {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n}\n\n/**\n * Clear fe-libs' resolved-response cache and in-flight de-dupe map so the next\n * read of any query hits the gateway. Safe to call unconditionally; a no-op\n * before the caches have been created.\n */\nexport function clearGraphqlResponseCache(): void {\n const g = globalThis as unknown as GraphqlFetchGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n}\n"],"mappings":";AA8BA,SAAgB,IAAkC;CAChD,IAAM,IAAI;CAEV,AADA,EAAE,6BAA6B,MAAM,GACrC,EAAE,gCAAgC,MAAM;AAC1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"toast.js","names":[],"sources":["../../../src/comradecircle/lib/toast.ts"],"sourcesContent":["import { toast } from 'sonner';\n\n/**\n * Module-wide toast API. sonner is a global singleton; the matching <Toaster>\n * is owned by the HOST app shell. Every page/component surfaces feedback through\n * these helpers instead of hand-rolling fixed-position toast markup, so\n * success/error notifications stay consistent with the rest of the platform.\n *\n * Durations follow the notification spec: success/info auto-dismiss, errors stay\n * until the user dismisses them (sonner renders a close button and announces via\n * aria-live).\n */\nexport const notify = {\n success(message: string, description?: string): void {\n toast.success(message, { description, duration: 4000 });\n },\n info(message: string, description?: string): void {\n toast.info(message, { description, duration: 4000 });\n },\n warning(message: string, description?: string): void {\n toast.warning(message, { description, duration: 8000 });\n },\n error(message: string, description?: string): void {\n toast.error(message, { description, duration: Infinity, closeButton: true });\n },\n dismiss(id?: string | number): void {\n toast.dismiss(id);\n },\n};\n\n/** Normalize an unknown thrown value into a human-readable message. */\nexport function describeError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n return 'Something went wrong. Please try again.';\n}\n\nexport { toast };\n"],"mappings":";;AAYA,IAAa,IAAS;CACpB,QAAQ,GAAiB,GAA4B;AACnD,IAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEzD,KAAK,GAAiB,GAA4B;AAChD,IAAM,KAAK,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEtD,QAAQ,GAAiB,GAA4B;AACnD,IAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEzD,MAAM,GAAiB,GAA4B;AACjD,IAAM,MAAM,GAAS;GAAE;GAAa,UAAU;GAAU,aAAa;GAAM,CAAC;;CAE9E,QAAQ,GAA4B;AAClC,IAAM,QAAQ,EAAG;;CAEpB;AAGD,SAAgB,EAAc,GAAwB;AAGpD,QAFI,aAAiB,QAAc,EAAM,UACrC,OAAO,KAAU,WAAiB,IAC/B"}
1
+ {"version":3,"file":"toast.js","names":[],"sources":["../../../src/comradecircle/lib/toast.ts"],"sourcesContent":["import { toast } from 'sonner';\n\n/**\n * Module-wide toast API. sonner is a global singleton; the matching <Toaster>\n * is owned by the HOST app shell. Every page/component surfaces feedback through\n * these helpers instead of hand-rolling fixed-position toast markup, so\n * success/error notifications stay consistent with the rest of the platform.\n *\n * Durations follow the notification spec: success/info auto-dismiss, errors stay\n * until the user dismisses them (sonner renders a close button and announces via\n * aria-live).\n */\nexport const notify = {\n success(message: string, description?: string): void {\n toast.success(message, { description, duration: 4000 });\n },\n info(message: string, description?: string): void {\n toast.info(message, { description, duration: 4000 });\n },\n warning(message: string, description?: string): void {\n toast.warning(message, { description, duration: 8000 });\n },\n error(message: string, description?: string): void {\n toast.error(message, { description, duration: Infinity, closeButton: true });\n },\n dismiss(id?: string | number): void {\n toast.dismiss(id);\n },\n};\n\n/** Normalize an unknown thrown value into a human-readable message. */\nexport function describeError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n return 'Something went wrong. Please try again.';\n}\n\nexport { toast };\n"],"mappings":";;AAYA,IAAa,IAAS;CACpB,QAAQ,GAAiB,GAA4B;EACnD,EAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;EAAK,CAAC;CACxD;CACA,KAAK,GAAiB,GAA4B;EAChD,EAAM,KAAK,GAAS;GAAE;GAAa,UAAU;EAAK,CAAC;CACrD;CACA,QAAQ,GAAiB,GAA4B;EACnD,EAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;EAAK,CAAC;CACxD;CACA,MAAM,GAAiB,GAA4B;EACjD,EAAM,MAAM,GAAS;GAAE;GAAa,UAAU;GAAU,aAAa;EAAK,CAAC;CAC7E;CACA,QAAQ,GAA4B;EAClC,EAAM,QAAQ,CAAE;CAClB;AACF;AAGA,SAAgB,EAAc,GAAwB;CAGpD,OAFI,aAAiB,QAAc,EAAM,UACrC,OAAO,KAAU,WAAiB,IAC/B;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"FeedPage.js","names":[],"sources":["../../../src/comradecircle/pages/FeedPage.tsx"],"sourcesContent":["/**\n * FeedPage — the member's unified home feed.\n * @module comradecircle/pages\n *\n * Reads `comMyFeed` (published posts from every channel, member, and circle the\n * caller follows, newest first). A new member with no follows sees a guided\n * empty state pointing at circle & member discovery.\n */\n\nimport { Newspaper, Compass, UsersRound } from 'lucide-react';\nimport { Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { PostCard } from '../components/PostCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { ErrorView } from '../components/StateViews';\nimport { Pagination } from '../components/Pagination';\nimport { useComMyFeed, useCursorPager } from '../hooks';\nimport { useComradeCircle } from '../context';\n\nconst PAGE_SIZE = 20;\n\n/**\n * Motion for each post as the feed 'assembles' on arrival. Fully neutralised\n * under `prefers-reduced-motion` (every class is `motion-safe:`); `fill-mode-both`\n * holds the hidden start state through the per-card `animationDelay`.\n */\nconst POST_ENTER =\n 'motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-3 ' +\n 'motion-safe:duration-500 motion-safe:ease-out motion-safe:fill-mode-both';\n\n/** Widths that give each skeleton post a natural, non-uniform body. */\nconst SKELETON_LINES = [\n ['w-11/12', 'w-3/4'],\n ['w-full', 'w-10/12', 'w-2/3'],\n ['w-5/6', 'w-1/2'],\n] as const;\n\n/**\n * A stack of post-card-shaped skeletons that mirrors the final feed layout, so\n * the transition from loading → loaded doesn't shift the column.\n */\nfunction FeedSkeleton() {\n return (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-4\" aria-hidden=\"true\">\n {SKELETON_LINES.map((lines, i) => (\n <GlassCard key={i} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n <Skeleton className=\"h-10 w-10 shrink-0 rounded-full\" />\n <div className=\"flex-1 space-y-2 pt-1\">\n <Skeleton className=\"h-3.5 w-32\" />\n <Skeleton className=\"h-3 w-20\" />\n </div>\n </div>\n <div className=\"mt-4 space-y-2.5\">\n {lines.map((w, j) => (\n <Skeleton key={j} className={`h-3 ${w}`} />\n ))}\n </div>\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <div className=\"flex items-center gap-2\">\n <Skeleton className=\"h-7 w-14 rounded-full\" />\n <Skeleton className=\"h-7 w-14 rounded-full\" />\n <Skeleton className=\"h-7 w-14 rounded-full\" />\n </div>\n <Skeleton className=\"h-7 w-24 rounded-full\" />\n </div>\n </GlassCard>\n ))}\n </div>\n );\n}\n\nexport function FeedPage() {\n const { navigateTo } = useComradeCircle();\n const pager = useCursorPager();\n const { data, isLoading, isError, refetch } = useComMyFeed({\n limit: PAGE_SIZE,\n cursor: pager.cursor,\n });\n\n const posts = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Home\"\n titleGradient\n aurora\n description=\"Posts from the circles, channels, and members you follow — newest first.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigateTo('circles')}>\n <Compass className=\"mr-1.5 h-4 w-4\" /> Discover circles\n </Button>\n }\n >\n {isLoading ? (\n <FeedSkeleton />\n ) : isError ? (\n <ErrorView message=\"We couldn't load your feed.\" onRetry={() => refetch()} />\n ) : posts.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-notifications\"\n icon={<Newspaper className=\"h-10 w-10\" />}\n title=\"Your feed is quiet\"\n description=\"Follow circles and members to fill your home feed with their latest posts, events, and discussions.\"\n action={\n <div className=\"flex flex-wrap items-center justify-center gap-3\">\n <Button onClick={() => navigateTo('circles')}>\n <Compass className=\"mr-1.5 h-4 w-4\" /> Explore circles\n </Button>\n <Button variant=\"outline\" onClick={() => navigateTo('members')}>\n <UsersRound className=\"mr-1.5 h-4 w-4\" /> Find members\n </Button>\n </div>\n }\n />\n ) : (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-4\">\n {posts.map((post, index) => (\n <div\n key={post.id}\n className={POST_ENTER}\n style={{ animationDelay: `${Math.min(index, 8) * 70}ms` }}\n >\n <PostCard post={post} />\n </div>\n ))}\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={posts.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,IAAM,IAAY,IAOZ,IACJ,0JAII,IAAiB;CACrB,CAAC,WAAW,QAAQ;CACpB;EAAC;EAAU;EAAW;EAAQ;CAC9B,CAAC,SAAS,QAAQ;CACnB;AAMD,SAAS,IAAe;AACtB,QACE,kBAAC,OAAD;EAAK,WAAU;EAAwC,eAAY;YAChE,EAAe,KAAK,GAAO,MAC1B,kBAAC,GAAD;GAAmB,WAAU;GAAQ,WAAU;aAA/C;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,WAAU,mCAAoC,CAAA,EACxD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,EACnC,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,CAC7B;QACF;;IACN,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,KAAK,GAAG,MACb,kBAAC,GAAD,EAAkB,WAAW,OAAO,KAAO,EAA5B,EAA4B,CAC3C;KACE,CAAA;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAU,WAAU,yBAA0B,CAAA;OAC9C,kBAAC,GAAD,EAAU,WAAU,yBAA0B,CAAA;OAC9C,kBAAC,GAAD,EAAU,WAAU,yBAA0B,CAAA;OAC1C;SACN,kBAAC,GAAD,EAAU,WAAU,yBAA0B,CAAA,CAC1C;;IACI;KArBI,EAqBJ,CACZ;EACE,CAAA;;AAIV,SAAgB,IAAW;CACzB,IAAM,EAAE,kBAAe,GAAkB,EACnC,IAAQ,GAAgB,EACxB,EAAE,SAAM,cAAW,YAAS,eAAY,EAAa;EACzD,OAAO;EACP,QAAQ,EAAM;EACf,CAAC,EAEI,IAAQ,GAAM,SAAS,EAAE;AAE/B,QACE,kBAAC,GAAD;EACE,OAAM;EACN,eAAA;EACA,QAAA;EACA,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,eAAe,EAAW,UAAU;aAA9D,CACE,kBAAC,GAAD,EAAS,WAAU,kBAAmB,CAAA,EAAA,oBAC/B;;YAGV,IACC,kBAAC,GAAD,EAAgB,CAAA,GACd,IACF,kBAAC,GAAD;GAAW,SAAQ;GAA8B,eAAe,GAAS;GAAI,CAAA,GAC3E,EAAM,WAAW,KAAK,EAAM,SAAS,IACvC,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAW,WAAU,aAAc,CAAA;GACzC,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAQ,eAAe,EAAW,UAAU;eAA5C,CACE,kBAAC,GAAD,EAAS,WAAU,kBAAmB,CAAA,EAAA,mBAC/B;QACT,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAW,UAAU;eAA9D,CACE,kBAAC,GAAD,EAAY,WAAU,kBAAmB,CAAA,EAAA,gBAClC;OACL;;GAER,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAM,KAAK,GAAM,MAChB,kBAAC,OAAD;IAEE,WAAW;IACX,OAAO,EAAE,gBAAgB,GAAG,KAAK,IAAI,GAAO,EAAE,GAAG,GAAG,KAAK;cAEzD,kBAAC,GAAD,EAAgB,SAAQ,CAAA;IACpB,EALC,EAAK,GAKN,CACN,EACF,kBAAC,GAAD;IACE,MAAM,EAAM;IACZ,UAAU;IACV,WAAW,EAAM;IACjB,SAAS,GAAM,SAAS,WAAW;IACnC,eAAe,MAAW,EAAM,aAAa,GAAQ,GAAM,SAAS,OAAO;IAC3E,CAAA,CACE;;EAEG,CAAA"}
1
+ {"version":3,"file":"FeedPage.js","names":[],"sources":["../../../src/comradecircle/pages/FeedPage.tsx"],"sourcesContent":["/**\n * FeedPage — the member's unified home feed.\n * @module comradecircle/pages\n *\n * Reads `comMyFeed` (published posts from every channel, member, and circle the\n * caller follows, newest first). A new member with no follows sees a guided\n * empty state pointing at circle & member discovery.\n */\n\nimport { Newspaper, Compass, UsersRound } from 'lucide-react';\nimport { Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { PostCard } from '../components/PostCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { ErrorView } from '../components/StateViews';\nimport { Pagination } from '../components/Pagination';\nimport { useComMyFeed, useCursorPager } from '../hooks';\nimport { useComradeCircle } from '../context';\n\nconst PAGE_SIZE = 20;\n\n/**\n * Motion for each post as the feed 'assembles' on arrival. Fully neutralised\n * under `prefers-reduced-motion` (every class is `motion-safe:`); `fill-mode-both`\n * holds the hidden start state through the per-card `animationDelay`.\n */\nconst POST_ENTER =\n 'motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-3 ' +\n 'motion-safe:duration-500 motion-safe:ease-out motion-safe:fill-mode-both';\n\n/** Widths that give each skeleton post a natural, non-uniform body. */\nconst SKELETON_LINES = [\n ['w-11/12', 'w-3/4'],\n ['w-full', 'w-10/12', 'w-2/3'],\n ['w-5/6', 'w-1/2'],\n] as const;\n\n/**\n * A stack of post-card-shaped skeletons that mirrors the final feed layout, so\n * the transition from loading → loaded doesn't shift the column.\n */\nfunction FeedSkeleton() {\n return (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-4\" aria-hidden=\"true\">\n {SKELETON_LINES.map((lines, i) => (\n <GlassCard key={i} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n <Skeleton className=\"h-10 w-10 shrink-0 rounded-full\" />\n <div className=\"flex-1 space-y-2 pt-1\">\n <Skeleton className=\"h-3.5 w-32\" />\n <Skeleton className=\"h-3 w-20\" />\n </div>\n </div>\n <div className=\"mt-4 space-y-2.5\">\n {lines.map((w, j) => (\n <Skeleton key={j} className={`h-3 ${w}`} />\n ))}\n </div>\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <div className=\"flex items-center gap-2\">\n <Skeleton className=\"h-7 w-14 rounded-full\" />\n <Skeleton className=\"h-7 w-14 rounded-full\" />\n <Skeleton className=\"h-7 w-14 rounded-full\" />\n </div>\n <Skeleton className=\"h-7 w-24 rounded-full\" />\n </div>\n </GlassCard>\n ))}\n </div>\n );\n}\n\nexport function FeedPage() {\n const { navigateTo } = useComradeCircle();\n const pager = useCursorPager();\n const { data, isLoading, isError, refetch } = useComMyFeed({\n limit: PAGE_SIZE,\n cursor: pager.cursor,\n });\n\n const posts = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Home\"\n titleGradient\n aurora\n description=\"Posts from the circles, channels, and members you follow — newest first.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigateTo('circles')}>\n <Compass className=\"mr-1.5 h-4 w-4\" /> Discover circles\n </Button>\n }\n >\n {isLoading ? (\n <FeedSkeleton />\n ) : isError ? (\n <ErrorView message=\"We couldn't load your feed.\" onRetry={() => refetch()} />\n ) : posts.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-notifications\"\n icon={<Newspaper className=\"h-10 w-10\" />}\n title=\"Your feed is quiet\"\n description=\"Follow circles and members to fill your home feed with their latest posts, events, and discussions.\"\n action={\n <div className=\"flex flex-wrap items-center justify-center gap-3\">\n <Button onClick={() => navigateTo('circles')}>\n <Compass className=\"mr-1.5 h-4 w-4\" /> Explore circles\n </Button>\n <Button variant=\"outline\" onClick={() => navigateTo('members')}>\n <UsersRound className=\"mr-1.5 h-4 w-4\" /> Find members\n </Button>\n </div>\n }\n />\n ) : (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-4\">\n {posts.map((post, index) => (\n <div\n key={post.id}\n className={POST_ENTER}\n style={{ animationDelay: `${Math.min(index, 8) * 70}ms` }}\n >\n <PostCard post={post} />\n </div>\n ))}\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={posts.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,IAAM,IAAY,IAOZ,IACJ,0JAII,IAAiB;CACrB,CAAC,WAAW,OAAO;CACnB;EAAC;EAAU;EAAW;CAAO;CAC7B,CAAC,SAAS,OAAO;AACnB;AAMA,SAAS,IAAe;CACtB,OACE,kBAAC,OAAD;EAAK,WAAU;EAAwC,eAAY;YAChE,EAAe,KAAK,GAAO,MAC1B,kBAAC,GAAD;GAAmB,WAAU;GAAQ,WAAU;aAA/C;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,WAAU,kCAAmC,CAAA,GACvD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,GAClC,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,CAC7B;OACF;;IACL,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,KAAK,GAAG,MACb,kBAAC,GAAD,EAAkB,WAAW,OAAO,IAAM,GAA3B,CAA2B,CAC3C;IACE,CAAA;IACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA;OAC7C,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA;OAC7C,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA;MAC1C;SACL,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA,CAC1C;;GACI;KArBK,CAqBL,CACZ;CACE,CAAA;AAET;AAEA,SAAgB,IAAW;CACzB,IAAM,EAAE,kBAAe,EAAiB,GAClC,IAAQ,EAAe,GACvB,EAAE,SAAM,cAAW,YAAS,eAAY,EAAa;EACzD,OAAO;EACP,QAAQ,EAAM;CAChB,CAAC,GAEK,IAAQ,GAAM,SAAS,CAAC;CAE9B,OACE,kBAAC,GAAD;EACE,OAAM;EACN,eAAA;EACA,QAAA;EACA,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,eAAe,EAAW,SAAS;aAA7D,CACE,kBAAC,GAAD,EAAS,WAAU,iBAAkB,CAAA,GAAC,mBAChC;;YAGT,IACC,kBAAC,GAAD,CAAe,CAAA,IACb,IACF,kBAAC,GAAD;GAAW,SAAQ;GAA8B,eAAe,EAAQ;EAAI,CAAA,IAC1E,EAAM,WAAW,KAAK,EAAM,SAAS,IACvC,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAW,WAAU,YAAa,CAAA;GACxC,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAQ,eAAe,EAAW,SAAS;eAA3C,CACE,kBAAC,GAAD,EAAS,WAAU,iBAAkB,CAAA,GAAC,kBAChC;QACR,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAW,SAAS;eAA7D,CACE,kBAAC,GAAD,EAAY,WAAU,iBAAkB,CAAA,GAAC,eACnC;MACL;;EAER,CAAA,IAED,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAM,KAAK,GAAM,MAChB,kBAAC,OAAD;IAEE,WAAW;IACX,OAAO,EAAE,gBAAgB,GAAG,KAAK,IAAI,GAAO,CAAC,IAAI,GAAG,IAAI;cAExD,kBAAC,GAAD,EAAgB,QAAO,CAAA;GACpB,GALE,EAAK,EAKP,CACN,GACD,kBAAC,GAAD;IACE,MAAM,EAAM;IACZ,UAAU;IACV,WAAW,EAAM;IACjB,SAAS,GAAM,SAAS,WAAW;IACnC,eAAe,MAAW,EAAM,aAAa,GAAQ,GAAM,SAAS,MAAM;GAC3E,CAAA,CACE;;CAEG,CAAA;AAEhB"}
@@ -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":"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"}
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":"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"}
@@ -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/** 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 +1 @@
1
- {"version":3,"file":"EventDetailPage.js","names":[],"sources":["../../../../src/comradecircle/pages/events/EventDetailPage.tsx"],"sourcesContent":["/**\n * EventDetailPage — a single community event.\n * @module comradecircle/pages/events\n *\n * Reads `comEvent` (with its RSVPs). Members RSVP (GOING / INTERESTED /\n * WAITLISTED / NOT_GOING) while the event is SCHEDULED or LIVE; the host drives\n * the lifecycle (SCHEDULED → LIVE → COMPLETED, or CANCELLED). Person references\n * always resolve through {@link MemberIdentity} — never a raw actor id.\n */\n\nimport {\n ArrowLeft,\n CalendarDays,\n MapPin,\n Video,\n Users,\n Radio,\n CheckCircle2,\n Check,\n Ban,\n Tag,\n} from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { MemberIdentity } from '../../components/MemberIdentity';\nimport { MiniBar } from '../../components/MiniBar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComEvent, useComRsvpEvent, useComTransitionEvent } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatDateTime, formatNumber, formatEnumLabel } from '../../utils';\nimport { cn } from '../../utils/cn';\nimport { notify, describeError } from '../../lib/toast';\nimport { useParams } from 'react-router-dom';\nimport type { ComEventStatus, ComRsvpStatus } from '../../types';\n\nconst RSVP_OPTIONS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'Going' },\n { status: 'INTERESTED', label: 'Interested' },\n { status: 'WAITLISTED', label: 'Waitlist' },\n { status: 'NOT_GOING', label: \"Can't go\" },\n];\n\n/** Attendee display order: confirmed first, declines last. */\nconst RSVP_ORDER: Record<ComRsvpStatus, number> = {\n GOING: 0,\n INTERESTED: 1,\n WAITLISTED: 2,\n NOT_GOING: 3,\n};\n\n/** Facet labels for the one-line attendee summary. */\nconst RSVP_FACETS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'going' },\n { status: 'INTERESTED', label: 'interested' },\n { status: 'WAITLISTED', label: 'waitlisted' },\n { status: 'NOT_GOING', label: \"can't go\" },\n];\n\nexport function EventDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { currentUser, navigateTo } = useComradeCircle();\n const { data: event, isLoading, isError, refetch } = useComEvent(id);\n const rsvp = useComRsvpEvent();\n const transition = useComTransitionEvent();\n\n const backButton = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('events')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <LoadingView label=\"Loading event…\" />\n </PageLayout>\n );\n }\n\n if (isError || !event) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <ErrorView\n title=\"Event not found\"\n message=\"This event may have been cancelled, or you may not have access to it.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n const isHost = Boolean(currentUser?.id && currentUser.id === event.hostId);\n const isLive = event.status === 'LIVE';\n const canRsvp = event.status === 'SCHEDULED' || event.status === 'LIVE';\n const isOnlineish = event.mode === 'ONLINE' || event.mode === 'HYBRID';\n const isInPersonish = event.mode === 'IN_PERSON' || event.mode === 'HYBRID';\n const showJoin = isLive && isOnlineish && Boolean(event.joinUrl);\n const myRsvp = currentUser?.id\n ? event.rsvps.find((r) => r.memberId === currentUser.id)\n : undefined;\n\n // Attendance / capacity — derived from real rsvpCount + capacity only.\n const capacity = event.capacity ?? null;\n const fillPct =\n capacity && capacity > 0 ? Math.min(100, (event.rsvpCount / capacity) * 100) : null;\n const nearFull = fillPct != null && fillPct >= 80;\n const isFull = fillPct != null && fillPct >= 100;\n const capacityLabel = fillPct == null ? '' : isFull ? 'Full' : `${Math.round(fillPct)}% full`;\n const capacityBar = isFull\n ? 'bg-status-error-text'\n : nearFull\n ? 'bg-status-warning-text'\n : 'bg-accent';\n const capacityTone = isFull\n ? 'text-status-error-text'\n : nearFull\n ? 'text-status-warning-text'\n : 'text-text-tertiary';\n\n // Attendees — sort GOING → INTERESTED → WAITLISTED → NOT_GOING; facet summary.\n const rsvpCounts = event.rsvps.reduce<Record<ComRsvpStatus, number>>(\n (acc, r) => {\n acc[r.status] += 1;\n return acc;\n },\n { GOING: 0, INTERESTED: 0, WAITLISTED: 0, NOT_GOING: 0 }\n );\n const sortedRsvps = [...event.rsvps].sort((a, b) => RSVP_ORDER[a.status] - RSVP_ORDER[b.status]);\n const facetSummary = RSVP_FACETS.filter((f) => rsvpCounts[f.status] > 0)\n .map((f) => `${formatNumber(rsvpCounts[f.status])} ${f.label}`)\n .join(' · ');\n\n const doRsvp = async (status: ComRsvpStatus) => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status });\n notify.success('RSVP updated', `${formatEnumLabel(status)} · ${event.title}`);\n } catch (err) {\n notify.error('Could not RSVP', describeError(err));\n }\n };\n\n const doTransition = async (status: ComEventStatus, successMessage: string) => {\n try {\n await transition.mutateAsync({ id: event.id, status });\n notify.success(successMessage, event.title);\n } catch (err) {\n notify.error('Could not update the event', describeError(err));\n }\n };\n\n const scheduleText = event.endsAt\n ? `${formatDateTime(event.startsAt)} – ${formatDateTime(event.endsAt)}`\n : formatDateTime(event.startsAt);\n\n return (\n <PageLayout title={event.title} actions={backButton}>\n <div className=\"mx-auto flex max-w-3xl flex-col gap-6\">\n {/* Overview */}\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'flex flex-col gap-5 p-5 sm:p-6',\n isLive && 'ring-1 ring-[var(--color-accent-soft)]'\n )}\n >\n {/* Motion-safe live ring — a pulsing accent halo, gated for reduced motion. */}\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 rounded-card ring-2 ring-accent/60 motion-safe:animate-pulse\"\n />\n )}\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={event.status} />\n <StatusBadge status={event.mode} />\n </div>\n\n {/* Prominent live-join CTA, promoted near the header. */}\n {showJoin && event.joinUrl && (\n <Button asChild size=\"lg\" className=\"w-full justify-center sm:w-auto sm:self-start\">\n <a href={event.joinUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n <Video className=\"h-4 w-4\" /> Join the live event\n </a>\n </Button>\n )}\n\n {event.description && (\n <p className=\"whitespace-pre-wrap text-sm text-text-secondary\">{event.description}</p>\n )}\n\n <dl className=\"flex flex-col gap-3 text-sm\">\n {/* Schedule */}\n <div className=\"flex items-start gap-2.5\">\n <CalendarDays className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">When</dt>\n <dd className=\"mt-0.5 text-text-primary\">{scheduleText}</dd>\n </div>\n </div>\n\n {/* Location (in-person / hybrid) */}\n {isInPersonish && event.location && (\n <div className=\"flex items-start gap-2.5\">\n <MapPin className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">Where</dt>\n <dd className=\"mt-0.5 text-text-primary\">{event.location}</dd>\n </div>\n </div>\n )}\n\n {/* Online join link (online / hybrid) */}\n {isOnlineish && (\n <div className=\"flex items-start gap-2.5\">\n <Video className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0\">\n <dt className=\"text-text-tertiary\">Online</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {showJoin ? (\n <span className=\"font-medium text-accent\">\n The join link is live — use the button above.\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {isLive\n ? 'A join link has not been provided.'\n : 'The join link becomes available when the event goes live.'}\n </span>\n )}\n </dd>\n </div>\n </div>\n )}\n\n {/* Attendance */}\n <div className=\"flex items-start gap-2.5\">\n <Users className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0 flex-1\">\n <dt className=\"text-text-tertiary\">Attending</dt>\n <dd className=\"mt-0.5 flex flex-wrap items-baseline gap-x-2 text-text-primary\">\n <span>\n {formatNumber(event.rsvpCount)}\n {capacity ? ` / ${formatNumber(capacity)}` : ''}\n </span>\n {capacityLabel && (\n <span className={cn('text-xs font-semibold', capacityTone)}>\n {capacityLabel}\n </span>\n )}\n </dd>\n {fillPct != null && (\n <MiniBar value={fillPct} barClassName={capacityBar} className=\"mt-2 max-w-xs\" />\n )}\n </div>\n </div>\n </dl>\n\n {/* Tags */}\n {event.tags.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Tag className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n {event.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Host */}\n <div className=\"border-t border-border-seam pt-4\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Hosted by\n </p>\n <MemberIdentity memberId={event.hostId} variant=\"row\" size=\"md\" showPresence />\n </div>\n </GlassCard>\n\n {/* RSVP controls */}\n {canRsvp && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Your RSVP</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Let the host know whether you&apos;ll attend.\n </p>\n <div className=\"mt-4 flex flex-col gap-2 sm:flex-row sm:flex-wrap\">\n {RSVP_OPTIONS.map((opt) => {\n const active = myRsvp?.status === opt.status;\n return (\n <Button\n key={opt.status}\n variant={active ? 'default' : 'outline'}\n onClick={() => doRsvp(opt.status)}\n disabled={rsvp.isPending}\n aria-pressed={active}\n className={cn(\n 'w-full justify-center sm:w-auto',\n active && 'font-semibold shadow-elevation-1'\n )}\n >\n {active && <Check className=\"h-4 w-4\" />}\n {opt.label}\n </Button>\n );\n })}\n </div>\n </section>\n )}\n\n {/* Host lifecycle controls */}\n {isHost && (event.status === 'SCHEDULED' || event.status === 'LIVE') && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Host controls</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Move this event through its lifecycle.\n </p>\n <div className=\"mt-4 flex flex-wrap gap-2\">\n {event.status === 'SCHEDULED' && (\n <>\n <Button\n size=\"sm\"\n onClick={() => doTransition('LIVE', 'Event is now live')}\n disabled={transition.isPending}\n >\n <Radio className=\"mr-1.5 h-4 w-4\" /> Go live\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => doTransition('CANCELLED', 'Event cancelled')}\n disabled={transition.isPending}\n >\n <Ban className=\"mr-1.5 h-4 w-4\" /> Cancel\n </Button>\n </>\n )}\n {event.status === 'LIVE' && (\n <Button\n size=\"sm\"\n onClick={() => doTransition('COMPLETED', 'Event marked complete')}\n disabled={transition.isPending}\n >\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" /> Mark complete\n </Button>\n )}\n </div>\n </section>\n )}\n\n {/* Attendees */}\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Attendees</h2>\n {facetSummary && (\n <p className=\"mt-0.5 truncate text-xs text-text-tertiary\">{facetSummary}</p>\n )}\n </div>\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-text-tertiary\">\n <Users className=\"h-3.5 w-3.5\" /> {formatNumber(event.rsvps.length)}\n </span>\n </div>\n {event.rsvps.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No RSVPs yet\"\n description=\"Be the first to let the host know you're coming.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-2\">\n {sortedRsvps.map((r) => (\n <li\n key={r.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border-seam bg-bg-sunken px-4 py-2.5 transition-colors hover:border-[var(--color-accent-soft)]\"\n >\n <MemberIdentity memberId={r.memberId} variant=\"row\" size=\"sm\" />\n <div className=\"flex min-w-0 items-center gap-2\">\n {r.note && (\n <span className=\"max-w-[16rem] truncate text-xs text-text-tertiary\">\n “{r.note}”\n </span>\n )}\n <StatusBadge status={r.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EventDetailPage.js","names":[],"sources":["../../../../src/comradecircle/pages/events/EventDetailPage.tsx"],"sourcesContent":["/**\n * EventDetailPage — a single community event.\n * @module comradecircle/pages/events\n *\n * Reads `comEvent` (with its RSVPs). Members RSVP (GOING / INTERESTED /\n * WAITLISTED / NOT_GOING) while the event is SCHEDULED or LIVE; the host drives\n * the lifecycle (SCHEDULED → LIVE → COMPLETED, or CANCELLED). Person references\n * always resolve through {@link MemberIdentity} — never a raw actor id.\n */\n\nimport {\n ArrowLeft,\n CalendarDays,\n MapPin,\n Video,\n Users,\n Radio,\n CheckCircle2,\n Check,\n Ban,\n Tag,\n} from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { MemberIdentity } from '../../components/MemberIdentity';\nimport { MiniBar } from '../../components/MiniBar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useComEvent, useComRsvpEvent, useComTransitionEvent } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatDateTime, formatNumber, formatEnumLabel } from '../../utils';\nimport { cn } from '../../utils/cn';\nimport { notify, describeError } from '../../lib/toast';\nimport { useParams } from 'react-router-dom';\nimport type { ComEventStatus, ComRsvpStatus } from '../../types';\n\nconst RSVP_OPTIONS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'Going' },\n { status: 'INTERESTED', label: 'Interested' },\n { status: 'WAITLISTED', label: 'Waitlist' },\n { status: 'NOT_GOING', label: \"Can't go\" },\n];\n\n/** Attendee display order: confirmed first, declines last. */\nconst RSVP_ORDER: Record<ComRsvpStatus, number> = {\n GOING: 0,\n INTERESTED: 1,\n WAITLISTED: 2,\n NOT_GOING: 3,\n};\n\n/** Facet labels for the one-line attendee summary. */\nconst RSVP_FACETS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'going' },\n { status: 'INTERESTED', label: 'interested' },\n { status: 'WAITLISTED', label: 'waitlisted' },\n { status: 'NOT_GOING', label: \"can't go\" },\n];\n\nexport function EventDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { currentUser, navigateTo } = useComradeCircle();\n const { data: event, isLoading, isError, refetch } = useComEvent(id);\n const rsvp = useComRsvpEvent();\n const transition = useComTransitionEvent();\n\n const backButton = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('events')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <LoadingView label=\"Loading event…\" />\n </PageLayout>\n );\n }\n\n if (isError || !event) {\n return (\n <PageLayout title=\"Event\" actions={backButton}>\n <ErrorView\n title=\"Event not found\"\n message=\"This event may have been cancelled, or you may not have access to it.\"\n onRetry={() => refetch()}\n />\n </PageLayout>\n );\n }\n\n const isHost = Boolean(currentUser?.id && currentUser.id === event.hostId);\n const isLive = event.status === 'LIVE';\n const canRsvp = event.status === 'SCHEDULED' || event.status === 'LIVE';\n const isOnlineish = event.mode === 'ONLINE' || event.mode === 'HYBRID';\n const isInPersonish = event.mode === 'IN_PERSON' || event.mode === 'HYBRID';\n const showJoin = isLive && isOnlineish && Boolean(event.joinUrl);\n const myRsvp = currentUser?.id\n ? event.rsvps.find((r) => r.memberId === currentUser.id)\n : undefined;\n\n // Attendance / capacity — derived from real rsvpCount + capacity only.\n const capacity = event.capacity ?? null;\n const fillPct =\n capacity && capacity > 0 ? Math.min(100, (event.rsvpCount / capacity) * 100) : null;\n const nearFull = fillPct != null && fillPct >= 80;\n const isFull = fillPct != null && fillPct >= 100;\n const capacityLabel = fillPct == null ? '' : isFull ? 'Full' : `${Math.round(fillPct)}% full`;\n const capacityBar = isFull\n ? 'bg-status-error-text'\n : nearFull\n ? 'bg-status-warning-text'\n : 'bg-accent';\n const capacityTone = isFull\n ? 'text-status-error-text'\n : nearFull\n ? 'text-status-warning-text'\n : 'text-text-tertiary';\n\n // Attendees — sort GOING → INTERESTED → WAITLISTED → NOT_GOING; facet summary.\n const rsvpCounts = event.rsvps.reduce<Record<ComRsvpStatus, number>>(\n (acc, r) => {\n acc[r.status] += 1;\n return acc;\n },\n { GOING: 0, INTERESTED: 0, WAITLISTED: 0, NOT_GOING: 0 }\n );\n const sortedRsvps = [...event.rsvps].sort((a, b) => RSVP_ORDER[a.status] - RSVP_ORDER[b.status]);\n const facetSummary = RSVP_FACETS.filter((f) => rsvpCounts[f.status] > 0)\n .map((f) => `${formatNumber(rsvpCounts[f.status])} ${f.label}`)\n .join(' · ');\n\n const doRsvp = async (status: ComRsvpStatus) => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status });\n notify.success('RSVP updated', `${formatEnumLabel(status)} · ${event.title}`);\n } catch (err) {\n notify.error('Could not RSVP', describeError(err));\n }\n };\n\n const doTransition = async (status: ComEventStatus, successMessage: string) => {\n try {\n await transition.mutateAsync({ id: event.id, status });\n notify.success(successMessage, event.title);\n } catch (err) {\n notify.error('Could not update the event', describeError(err));\n }\n };\n\n const scheduleText = event.endsAt\n ? `${formatDateTime(event.startsAt)} – ${formatDateTime(event.endsAt)}`\n : formatDateTime(event.startsAt);\n\n return (\n <PageLayout title={event.title} actions={backButton}>\n <div className=\"mx-auto flex max-w-3xl flex-col gap-6\">\n {/* Overview */}\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'flex flex-col gap-5 p-5 sm:p-6',\n isLive && 'ring-1 ring-[var(--color-accent-soft)]'\n )}\n >\n {/* Motion-safe live ring — a pulsing accent halo, gated for reduced motion. */}\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 rounded-card ring-2 ring-accent/60 motion-safe:animate-pulse\"\n />\n )}\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={event.status} />\n <StatusBadge status={event.mode} />\n </div>\n\n {/* Prominent live-join CTA, promoted near the header. */}\n {showJoin && event.joinUrl && (\n <Button asChild size=\"lg\" className=\"w-full justify-center sm:w-auto sm:self-start\">\n <a href={event.joinUrl} target=\"_blank\" rel=\"noreferrer noopener\">\n <Video className=\"h-4 w-4\" /> Join the live event\n </a>\n </Button>\n )}\n\n {event.description && (\n <p className=\"whitespace-pre-wrap text-sm text-text-secondary\">{event.description}</p>\n )}\n\n <dl className=\"flex flex-col gap-3 text-sm\">\n {/* Schedule */}\n <div className=\"flex items-start gap-2.5\">\n <CalendarDays className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">When</dt>\n <dd className=\"mt-0.5 text-text-primary\">{scheduleText}</dd>\n </div>\n </div>\n\n {/* Location (in-person / hybrid) */}\n {isInPersonish && event.location && (\n <div className=\"flex items-start gap-2.5\">\n <MapPin className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div>\n <dt className=\"text-text-tertiary\">Where</dt>\n <dd className=\"mt-0.5 text-text-primary\">{event.location}</dd>\n </div>\n </div>\n )}\n\n {/* Online join link (online / hybrid) */}\n {isOnlineish && (\n <div className=\"flex items-start gap-2.5\">\n <Video className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0\">\n <dt className=\"text-text-tertiary\">Online</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {showJoin ? (\n <span className=\"font-medium text-accent\">\n The join link is live — use the button above.\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {isLive\n ? 'A join link has not been provided.'\n : 'The join link becomes available when the event goes live.'}\n </span>\n )}\n </dd>\n </div>\n </div>\n )}\n\n {/* Attendance */}\n <div className=\"flex items-start gap-2.5\">\n <Users className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <div className=\"min-w-0 flex-1\">\n <dt className=\"text-text-tertiary\">Attending</dt>\n <dd className=\"mt-0.5 flex flex-wrap items-baseline gap-x-2 text-text-primary\">\n <span>\n {formatNumber(event.rsvpCount)}\n {capacity ? ` / ${formatNumber(capacity)}` : ''}\n </span>\n {capacityLabel && (\n <span className={cn('text-xs font-semibold', capacityTone)}>\n {capacityLabel}\n </span>\n )}\n </dd>\n {fillPct != null && (\n <MiniBar value={fillPct} barClassName={capacityBar} className=\"mt-2 max-w-xs\" />\n )}\n </div>\n </div>\n </dl>\n\n {/* Tags */}\n {event.tags.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Tag className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n {event.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Host */}\n <div className=\"border-t border-border-seam pt-4\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-tertiary\">\n Hosted by\n </p>\n <MemberIdentity memberId={event.hostId} variant=\"row\" size=\"md\" showPresence />\n </div>\n </GlassCard>\n\n {/* RSVP controls */}\n {canRsvp && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Your RSVP</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Let the host know whether you&apos;ll attend.\n </p>\n <div className=\"mt-4 flex flex-col gap-2 sm:flex-row sm:flex-wrap\">\n {RSVP_OPTIONS.map((opt) => {\n const active = myRsvp?.status === opt.status;\n return (\n <Button\n key={opt.status}\n variant={active ? 'default' : 'outline'}\n onClick={() => doRsvp(opt.status)}\n disabled={rsvp.isPending}\n aria-pressed={active}\n className={cn(\n 'w-full justify-center sm:w-auto',\n active && 'font-semibold shadow-elevation-1'\n )}\n >\n {active && <Check className=\"h-4 w-4\" />}\n {opt.label}\n </Button>\n );\n })}\n </div>\n </section>\n )}\n\n {/* Host lifecycle controls */}\n {isHost && (event.status === 'SCHEDULED' || event.status === 'LIVE') && (\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <h2 className=\"text-base font-semibold text-text-primary\">Host controls</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Move this event through its lifecycle.\n </p>\n <div className=\"mt-4 flex flex-wrap gap-2\">\n {event.status === 'SCHEDULED' && (\n <>\n <Button\n size=\"sm\"\n onClick={() => doTransition('LIVE', 'Event is now live')}\n disabled={transition.isPending}\n >\n <Radio className=\"mr-1.5 h-4 w-4\" /> Go live\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => doTransition('CANCELLED', 'Event cancelled')}\n disabled={transition.isPending}\n >\n <Ban className=\"mr-1.5 h-4 w-4\" /> Cancel\n </Button>\n </>\n )}\n {event.status === 'LIVE' && (\n <Button\n size=\"sm\"\n onClick={() => doTransition('COMPLETED', 'Event marked complete')}\n disabled={transition.isPending}\n >\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" /> Mark complete\n </Button>\n )}\n </div>\n </section>\n )}\n\n {/* Attendees */}\n <section className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Attendees</h2>\n {facetSummary && (\n <p className=\"mt-0.5 truncate text-xs text-text-tertiary\">{facetSummary}</p>\n )}\n </div>\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-text-tertiary\">\n <Users className=\"h-3.5 w-3.5\" /> {formatNumber(event.rsvps.length)}\n </span>\n </div>\n {event.rsvps.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No RSVPs yet\"\n description=\"Be the first to let the host know you're coming.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-2\">\n {sortedRsvps.map((r) => (\n <li\n key={r.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border-seam bg-bg-sunken px-4 py-2.5 transition-colors hover:border-[var(--color-accent-soft)]\"\n >\n <MemberIdentity memberId={r.memberId} variant=\"row\" size=\"sm\" />\n <div className=\"flex min-w-0 items-center gap-2\">\n {r.note && (\n <span className=\"max-w-[16rem] truncate text-xs text-text-tertiary\">\n “{r.note}”\n </span>\n )}\n <StatusBadge status={r.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}