@burdenoff/microfe-comradecircle 2026.819.1 → 2026.830.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comradecircle/components/AvatarUploadField.js +8 -8
- package/dist/comradecircle/components/CircleCard.js +4 -4
- package/dist/comradecircle/components/CommentThread.d.ts.map +1 -1
- package/dist/comradecircle/components/CommentThread.js +66 -61
- package/dist/comradecircle/components/CommentThread.js.map +1 -1
- package/dist/comradecircle/components/CreateCircleDialog.d.ts.map +1 -1
- package/dist/comradecircle/components/CreateCircleDialog.js +89 -88
- package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -1
- package/dist/comradecircle/components/CreateEventDialog.d.ts.map +1 -1
- package/dist/comradecircle/components/CreateEventDialog.js +28 -27
- package/dist/comradecircle/components/CreateEventDialog.js.map +1 -1
- package/dist/comradecircle/components/DraftsPanel.d.ts +32 -0
- package/dist/comradecircle/components/DraftsPanel.d.ts.map +1 -0
- package/dist/comradecircle/components/DraftsPanel.js +176 -0
- package/dist/comradecircle/components/DraftsPanel.js.map +1 -0
- package/dist/comradecircle/components/EventCard.js +5 -5
- package/dist/comradecircle/components/FileImage.js +5 -5
- package/dist/comradecircle/components/ImageAttachments.js +21 -21
- package/dist/comradecircle/components/MemberIdentity.js +4 -4
- package/dist/comradecircle/components/MobileBottomNav.d.ts.map +1 -1
- package/dist/comradecircle/components/MobileBottomNav.js +37 -30
- package/dist/comradecircle/components/MobileBottomNav.js.map +1 -1
- package/dist/comradecircle/components/Pagination.d.ts.map +1 -1
- package/dist/comradecircle/components/Pagination.js +37 -28
- package/dist/comradecircle/components/Pagination.js.map +1 -1
- package/dist/comradecircle/components/PollCard.js +13 -13
- package/dist/comradecircle/components/PostCard.d.ts.map +1 -1
- package/dist/comradecircle/components/PostCard.js +102 -89
- package/dist/comradecircle/components/PostCard.js.map +1 -1
- package/dist/comradecircle/components/PostComposer.d.ts +11 -5
- package/dist/comradecircle/components/PostComposer.d.ts.map +1 -1
- package/dist/comradecircle/components/PostComposer.js +60 -48
- package/dist/comradecircle/components/PostComposer.js.map +1 -1
- package/dist/comradecircle/components/ReactionBar.d.ts.map +1 -1
- package/dist/comradecircle/components/ReactionBar.js +65 -55
- package/dist/comradecircle/components/ReactionBar.js.map +1 -1
- package/dist/comradecircle/components/StateViews.d.ts.map +1 -1
- package/dist/comradecircle/components/StateViews.js +38 -30
- package/dist/comradecircle/components/StateViews.js.map +1 -1
- package/dist/comradecircle/components/UpcomingEvents.d.ts +26 -0
- package/dist/comradecircle/components/UpcomingEvents.d.ts.map +1 -0
- package/dist/comradecircle/components/UpcomingEvents.js +126 -0
- package/dist/comradecircle/components/UpcomingEvents.js.map +1 -0
- package/dist/comradecircle/components/index.d.ts +2 -0
- package/dist/comradecircle/components/index.d.ts.map +1 -1
- package/dist/comradecircle/hooks/index.d.ts +1 -0
- package/dist/comradecircle/hooks/index.d.ts.map +1 -1
- package/dist/comradecircle/hooks/index.js +1 -0
- package/dist/comradecircle/hooks/useTr.d.ts +18 -0
- package/dist/comradecircle/hooks/useTr.d.ts.map +1 -0
- package/dist/comradecircle/hooks/useTr.js +12 -0
- package/dist/comradecircle/hooks/useTr.js.map +1 -0
- package/dist/comradecircle/pages/FeedPage.d.ts +5 -0
- package/dist/comradecircle/pages/FeedPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/FeedPage.js +76 -62
- package/dist/comradecircle/pages/FeedPage.js.map +1 -1
- package/dist/comradecircle/pages/circles/CircleDetailPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/circles/CircleDetailPage.js +454 -409
- package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/circles/CirclesPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/circles/CirclesPage.js +124 -117
- package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
- package/dist/comradecircle/pages/events/EventDetailPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/events/EventDetailPage.js +182 -153
- package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/events/EventsPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/events/EventsPage.js +100 -91
- package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
- package/dist/comradecircle/pages/members/MemberProfilePage.d.ts.map +1 -1
- package/dist/comradecircle/pages/members/MemberProfilePage.js +172 -159
- package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
- package/dist/comradecircle/pages/members/MembersPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/members/MembersPage.js +103 -104
- package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
- package/dist/comradecircle/pages/monetization/MonetizationPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/monetization/MonetizationPage.js +319 -297
- package/dist/comradecircle/pages/monetization/MonetizationPage.js.map +1 -1
- package/dist/comradecircle/pages/posts/PostThreadPage.d.ts.map +1 -1
- package/dist/comradecircle/pages/posts/PostThreadPage.js +37 -32
- package/dist/comradecircle/pages/posts/PostThreadPage.js.map +1 -1
- package/dist/comradecircle/pages/profile/MyProfilePage.d.ts.map +1 -1
- package/dist/comradecircle/pages/profile/MyProfilePage.js +182 -177
- package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
- package/dist/comradecircle/utils/i18n.js +15 -0
- package/dist/comradecircle/utils/i18n.js.map +1 -0
- package/dist/comradecircle/utils/pageContext.d.ts.map +1 -1
- package/dist/comradecircle/utils/pageContext.js +3 -1
- package/dist/comradecircle/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -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, useComEventLive, 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';\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 // BOFF-4455: live status badge + attendee count without a manual refresh.\n useComEventLive(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'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 {\n useComEvent,\n useComEventLive,\n useComRsvpEvent,\n useComTransitionEvent,\n useTr,\n} 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';\nimport type { ComEventStatus, ComRsvpStatus } from '../../types';\n\nconst RSVP_OPTIONS: { status: ComRsvpStatus; labelKey: string; fallback: string }[] = [\n { status: 'GOING', labelKey: 'comradecircle.pages.eventDetail.rsvpGoing', fallback: 'Going' },\n {\n status: 'INTERESTED',\n labelKey: 'comradecircle.pages.eventDetail.rsvpInterested',\n fallback: 'Interested',\n },\n {\n status: 'WAITLISTED',\n labelKey: 'comradecircle.pages.eventDetail.rsvpWaitlist',\n fallback: 'Waitlist',\n },\n {\n status: 'NOT_GOING',\n labelKey: 'comradecircle.pages.eventDetail.rsvpNotGoing',\n fallback: \"Can't go\",\n },\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; labelKey: string; fallback: string }[] = [\n { status: 'GOING', labelKey: 'comradecircle.pages.eventDetail.facetGoing', fallback: 'going' },\n {\n status: 'INTERESTED',\n labelKey: 'comradecircle.pages.eventDetail.facetInterested',\n fallback: 'interested',\n },\n {\n status: 'WAITLISTED',\n labelKey: 'comradecircle.pages.eventDetail.facetWaitlisted',\n fallback: 'waitlisted',\n },\n {\n status: 'NOT_GOING',\n labelKey: 'comradecircle.pages.eventDetail.facetNotGoing',\n fallback: \"can't go\",\n },\n];\n\nexport function EventDetailPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n const { currentUser, navigateTo } = useComradeCircle();\n const { data: event, isLoading, isError, refetch } = useComEvent(id);\n // BOFF-4455: live status badge + attendee count without a manual refresh.\n useComEventLive(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\" /> {tr('common.back', 'Back')}\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title={tr('comradecircle.pages.eventDetail.title', 'Event')} actions={backButton}>\n <LoadingView label={tr('comradecircle.pages.eventDetail.loadingEvent', 'Loading event…')} />\n </PageLayout>\n );\n }\n\n if (isError || !event) {\n return (\n <PageLayout title={tr('comradecircle.pages.eventDetail.title', 'Event')} actions={backButton}>\n <ErrorView\n title={tr('comradecircle.pages.eventDetail.notFoundTitle', 'Event not found')}\n message={tr(\n 'comradecircle.pages.eventDetail.notFoundMessage',\n 'This event may have been cancelled, or you may not have access to it.'\n )}\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 =\n fillPct == null\n ? ''\n : isFull\n ? tr('comradecircle.pages.eventDetail.capacityFull', 'Full')\n : tr('comradecircle.pages.eventDetail.capacityPercent', '{{pct}}% full', {\n pct: Math.round(fillPct),\n });\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])} ${tr(f.labelKey, f.fallback)}`)\n .join(' · ');\n\n const doRsvp = async (status: ComRsvpStatus) => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status });\n notify.success(\n tr('comradecircle.pages.eventDetail.rsvpUpdated', 'RSVP updated'),\n `${formatEnumLabel(status)} · ${event.title}`\n );\n } catch (err) {\n notify.error(\n tr('comradecircle.pages.eventDetail.rsvpError', 'Could not RSVP'),\n describeError(err)\n );\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(\n tr('comradecircle.pages.eventDetail.transitionError', 'Could not update the event'),\n describeError(err)\n );\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\" />{' '}\n {tr('comradecircle.pages.eventDetail.joinLive', '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\">\n {tr('comradecircle.pages.eventDetail.whenLabel', 'When')}\n </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\">\n {tr('comradecircle.pages.eventDetail.whereLabel', 'Where')}\n </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\">\n {tr('comradecircle.pages.eventDetail.onlineLabel', 'Online')}\n </dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {showJoin ? (\n <span className=\"font-medium text-accent\">\n {tr(\n 'comradecircle.pages.eventDetail.joinLinkLive',\n 'The join link is live — use the button above.'\n )}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {isLive\n ? tr(\n 'comradecircle.pages.eventDetail.joinLinkNotProvided',\n 'A join link has not been provided.'\n )\n : tr(\n 'comradecircle.pages.eventDetail.joinLinkWhenLive',\n 'The join link becomes available when the event goes live.'\n )}\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\">\n {tr('comradecircle.pages.eventDetail.attendingLabel', 'Attending')}\n </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 {tr('comradecircle.pages.eventDetail.hostedBy', '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\">\n {tr('comradecircle.pages.eventDetail.yourRsvpTitle', 'Your RSVP')}\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tr(\n 'comradecircle.pages.eventDetail.rsvpPrompt',\n \"Let the host know whether you'll attend.\"\n )}\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 {tr(opt.labelKey, opt.fallback)}\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\">\n {tr('comradecircle.pages.eventDetail.hostControlsTitle', 'Host controls')}\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tr(\n 'comradecircle.pages.eventDetail.hostControlsPrompt',\n 'Move this event through its lifecycle.'\n )}\n </p>\n <div className=\"mt-4 flex flex-wrap gap-2\">\n {event.status === 'SCHEDULED' && (\n <>\n <Button\n size=\"sm\"\n onClick={() =>\n doTransition(\n 'LIVE',\n tr('comradecircle.pages.eventDetail.wentLive', 'Event is now live')\n )\n }\n disabled={transition.isPending}\n >\n <Radio className=\"mr-1.5 h-4 w-4\" />{' '}\n {tr('comradecircle.pages.eventDetail.goLive', 'Go live')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() =>\n doTransition(\n 'CANCELLED',\n tr('comradecircle.pages.eventDetail.cancelled', 'Event cancelled')\n )\n }\n disabled={transition.isPending}\n >\n <Ban className=\"mr-1.5 h-4 w-4\" />{' '}\n {tr('comradecircle.pages.eventDetail.cancelEvent', 'Cancel')}\n </Button>\n </>\n )}\n {event.status === 'LIVE' && (\n <Button\n size=\"sm\"\n onClick={() =>\n doTransition(\n 'COMPLETED',\n tr('comradecircle.pages.eventDetail.markedComplete', 'Event marked complete')\n )\n }\n disabled={transition.isPending}\n >\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" />{' '}\n {tr('comradecircle.pages.eventDetail.markComplete', '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\">\n {tr('comradecircle.pages.eventDetail.attendeesTitle', 'Attendees')}\n </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={tr('comradecircle.pages.eventDetail.noRsvpsTitle', 'No RSVPs yet')}\n description={tr(\n 'comradecircle.pages.eventDetail.noRsvpsDescription',\n \"Be the first to let the host know you're coming.\"\n )}\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventsPage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/events/EventsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"EventsPage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/events/EventsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwDH,wBAAgB,UAAU,4CA0HzB"}
|
|
@@ -1,137 +1,146 @@
|
|
|
1
1
|
import { useComradeCircle as e } from "../../context/ComradeCircleContext.js";
|
|
2
2
|
import "../../context/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { useComEvents as t } from "../../hooks/useComradeCircleApi.js";
|
|
4
|
+
import { useCursorPager as n } from "../../hooks/useCursorPager.js";
|
|
5
|
+
import { useTr as r } from "../../hooks/useTr.js";
|
|
6
6
|
import "../../hooks/index.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
7
|
+
import { PageLayout as i } from "../../components/PageLayout.js";
|
|
8
|
+
import { formatNumber as a } from "../../utils/formatters.js";
|
|
9
|
+
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
10
|
+
import { ErrorView as s } from "../../components/StateViews.js";
|
|
11
|
+
import { Pagination as c } from "../../components/Pagination.js";
|
|
12
|
+
import { EventCard as l } from "../../components/EventCard.js";
|
|
13
|
+
import { CreateEventDialog as u } from "../../components/CreateEventDialog.js";
|
|
14
|
+
import { FilterTabs as d } from "../../components/FilterTabs.js";
|
|
15
|
+
import { useState as f } from "react";
|
|
16
|
+
import { CalendarDays as p, Plus as m } from "lucide-react";
|
|
17
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
18
|
+
import { Button as _, GlassCard as v, Skeleton as y } from "@burdenoff/fe-libs/ui";
|
|
18
19
|
//#region src/comradecircle/pages/events/EventsPage.tsx
|
|
19
|
-
var
|
|
20
|
-
{
|
|
21
|
-
value: "ALL",
|
|
22
|
-
label: "All"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
value: "SCHEDULED",
|
|
26
|
-
label: "Scheduled"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
value: "LIVE",
|
|
30
|
-
label: "Live"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
value: "COMPLETED",
|
|
34
|
-
label: "Completed"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
value: "CANCELLED",
|
|
38
|
-
label: "Cancelled"
|
|
39
|
-
}
|
|
40
|
-
];
|
|
20
|
+
var b = 12;
|
|
41
21
|
function x() {
|
|
42
|
-
return /* @__PURE__ */
|
|
22
|
+
return /* @__PURE__ */ g(v, {
|
|
43
23
|
treatment: "plain",
|
|
44
24
|
className: "flex flex-col p-5",
|
|
45
25
|
"aria-hidden": "true",
|
|
46
26
|
children: [
|
|
47
|
-
/* @__PURE__ */
|
|
27
|
+
/* @__PURE__ */ g("div", {
|
|
48
28
|
className: "flex items-start justify-between gap-3",
|
|
49
|
-
children: [/* @__PURE__ */
|
|
29
|
+
children: [/* @__PURE__ */ h(y, { className: "h-5 w-20 rounded-full" }), /* @__PURE__ */ h(y, { className: "h-4 w-14" })]
|
|
50
30
|
}),
|
|
51
|
-
/* @__PURE__ */
|
|
52
|
-
/* @__PURE__ */
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
/* @__PURE__ */
|
|
31
|
+
/* @__PURE__ */ h(y, { className: "mt-2 h-5 w-3/5" }),
|
|
32
|
+
/* @__PURE__ */ h(y, { className: "mt-2 h-3.5 w-full" }),
|
|
33
|
+
/* @__PURE__ */ h(y, { className: "mt-1.5 h-3.5 w-4/5" }),
|
|
34
|
+
/* @__PURE__ */ g("div", {
|
|
55
35
|
className: "mt-3 flex flex-col gap-2",
|
|
56
|
-
children: [/* @__PURE__ */
|
|
36
|
+
children: [/* @__PURE__ */ h(y, { className: "h-4 w-40" }), /* @__PURE__ */ h(y, { className: "h-4 w-32" })]
|
|
57
37
|
}),
|
|
58
|
-
/* @__PURE__ */
|
|
59
|
-
/* @__PURE__ */
|
|
38
|
+
/* @__PURE__ */ h(y, { className: "mt-3 h-1 w-full rounded-full" }),
|
|
39
|
+
/* @__PURE__ */ g("div", {
|
|
60
40
|
className: "mt-4 flex items-center justify-end gap-2 border-t border-border-seam pt-3",
|
|
61
|
-
children: [/* @__PURE__ */
|
|
41
|
+
children: [/* @__PURE__ */ h(y, { className: "h-8 w-20 rounded-md" }), /* @__PURE__ */ h(y, { className: "h-8 w-24 rounded-md" })]
|
|
62
42
|
})
|
|
63
43
|
]
|
|
64
44
|
});
|
|
65
45
|
}
|
|
66
46
|
function S() {
|
|
67
|
-
return /* @__PURE__ */
|
|
47
|
+
return /* @__PURE__ */ h("div", {
|
|
68
48
|
className: "grid gap-4 md:grid-cols-2",
|
|
69
49
|
"aria-hidden": "true",
|
|
70
|
-
children: Array.from({ length: 4 }, (e, t) => /* @__PURE__ */
|
|
50
|
+
children: Array.from({ length: 4 }, (e, t) => /* @__PURE__ */ h(x, {}, t))
|
|
71
51
|
});
|
|
72
52
|
}
|
|
73
53
|
function C() {
|
|
74
|
-
let { navigateTo:
|
|
75
|
-
|
|
54
|
+
let { navigateTo: v } = e(), y = n(), x = r(), [C, w] = f("ALL"), [T, E] = f(!1), D = [
|
|
55
|
+
{
|
|
56
|
+
value: "ALL",
|
|
57
|
+
label: x("comradecircle.pages.events.filterAll", "All")
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
value: "SCHEDULED",
|
|
61
|
+
label: x("comradecircle.pages.events.filterScheduled", "Scheduled")
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: "LIVE",
|
|
65
|
+
label: x("comradecircle.pages.events.filterLive", "Live")
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
value: "COMPLETED",
|
|
69
|
+
label: x("comradecircle.pages.events.filterCompleted", "Completed")
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
value: "CANCELLED",
|
|
73
|
+
label: x("comradecircle.pages.events.filterCancelled", "Cancelled")
|
|
74
|
+
}
|
|
75
|
+
], { data: O, isLoading: k, isError: A, refetch: j } = t({
|
|
76
|
+
status: C === "ALL" ? void 0 : C,
|
|
76
77
|
pagination: {
|
|
77
|
-
limit:
|
|
78
|
-
cursor:
|
|
78
|
+
limit: b,
|
|
79
|
+
cursor: y.cursor
|
|
79
80
|
}
|
|
80
|
-
}),
|
|
81
|
-
|
|
82
|
-
},
|
|
83
|
-
return /* @__PURE__ */
|
|
84
|
-
title: "Events",
|
|
85
|
-
description: "Upcoming and past gatherings across all the circles you belong to.",
|
|
86
|
-
actions: /* @__PURE__ */
|
|
87
|
-
onClick: () =>
|
|
88
|
-
children: [
|
|
81
|
+
}), M = O?.items ?? [], N = O?.pageInfo.hasMore ?? !1, P = (e) => {
|
|
82
|
+
w(e), y.reset();
|
|
83
|
+
}, F = `${a(M.length)} ` + x(M.length === 1 ? "comradecircle.pages.events.eventCount" : "comradecircle.pages.events.eventsCount", M.length === 1 ? "event" : "events") + (y.page > 0 ? ` · ${x("comradecircle.pages.events.pageSuffix", "page {{page}}", { page: y.page + 1 })}` : N ? ` · ${x("comradecircle.pages.events.moreAvailable", "more available")}` : "");
|
|
84
|
+
return /* @__PURE__ */ g(i, {
|
|
85
|
+
title: x("comradecircle.pages.events.title", "Events"),
|
|
86
|
+
description: x("comradecircle.pages.events.description", "Upcoming and past gatherings across all the circles you belong to."),
|
|
87
|
+
actions: /* @__PURE__ */ g(_, {
|
|
88
|
+
onClick: () => E(!0),
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ h(m, { className: "mr-1.5 h-4 w-4" }),
|
|
91
|
+
" ",
|
|
92
|
+
x("comradecircle.pages.events.newEvent", "New event")
|
|
93
|
+
]
|
|
89
94
|
}),
|
|
90
95
|
children: [
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
-
options:
|
|
93
|
-
value:
|
|
94
|
-
onChange:
|
|
96
|
+
/* @__PURE__ */ h(d, {
|
|
97
|
+
options: D,
|
|
98
|
+
value: C,
|
|
99
|
+
onChange: P,
|
|
95
100
|
className: "mb-5"
|
|
96
101
|
}),
|
|
97
|
-
|
|
98
|
-
message: "We couldn't load events.",
|
|
99
|
-
onRetry: () =>
|
|
100
|
-
}) :
|
|
102
|
+
k ? /* @__PURE__ */ h(S, {}) : A ? /* @__PURE__ */ h(s, {
|
|
103
|
+
message: x("comradecircle.pages.events.errorMessage", "We couldn't load events."),
|
|
104
|
+
onRetry: () => j()
|
|
105
|
+
}) : M.length === 0 && y.page === 0 ? /* @__PURE__ */ h(o, {
|
|
101
106
|
illustration: "empty-data",
|
|
102
|
-
icon: /* @__PURE__ */
|
|
103
|
-
title:
|
|
104
|
-
description:
|
|
105
|
-
action: /* @__PURE__ */
|
|
106
|
-
onClick: () =>
|
|
107
|
-
children: [
|
|
107
|
+
icon: /* @__PURE__ */ h(p, { className: "h-10 w-10" }),
|
|
108
|
+
title: C === "ALL" ? x("comradecircle.pages.events.emptyTitleAll", "No events yet") : x("comradecircle.pages.events.emptyTitleFiltered", "No events match this filter"),
|
|
109
|
+
description: C === "ALL" ? x("comradecircle.pages.events.emptyDescriptionAll", "Schedule a gathering for one of your circles — online, in person, or hybrid.") : x("comradecircle.pages.events.emptyDescriptionFiltered", "Try a different status, or schedule a new gathering for one of your circles."),
|
|
110
|
+
action: /* @__PURE__ */ g(_, {
|
|
111
|
+
onClick: () => E(!0),
|
|
112
|
+
children: [
|
|
113
|
+
/* @__PURE__ */ h(m, { className: "mr-1.5 h-4 w-4" }),
|
|
114
|
+
" ",
|
|
115
|
+
x("comradecircle.pages.events.newEvent", "New event")
|
|
116
|
+
]
|
|
108
117
|
})
|
|
109
|
-
}) : /* @__PURE__ */
|
|
118
|
+
}) : /* @__PURE__ */ g("div", {
|
|
110
119
|
className: "flex flex-col gap-4",
|
|
111
120
|
children: [
|
|
112
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ h("p", {
|
|
113
122
|
className: "text-xs font-medium text-text-tertiary",
|
|
114
|
-
children:
|
|
123
|
+
children: F
|
|
115
124
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ h("div", {
|
|
117
126
|
className: "grid gap-4 md:grid-cols-2",
|
|
118
|
-
children:
|
|
127
|
+
children: M.map((e) => /* @__PURE__ */ h(l, {
|
|
119
128
|
event: e,
|
|
120
|
-
onOpen: () =>
|
|
129
|
+
onOpen: () => v("events/" + e.id)
|
|
121
130
|
}, e.id))
|
|
122
131
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
-
page:
|
|
125
|
-
pageSize:
|
|
126
|
-
itemCount:
|
|
127
|
-
hasMore:
|
|
128
|
-
onPageChange: (e) =>
|
|
132
|
+
/* @__PURE__ */ h(c, {
|
|
133
|
+
page: y.page,
|
|
134
|
+
pageSize: b,
|
|
135
|
+
itemCount: M.length,
|
|
136
|
+
hasMore: O?.pageInfo.hasMore ?? !1,
|
|
137
|
+
onPageChange: (e) => y.onPageChange(e, O?.pageInfo.cursor)
|
|
129
138
|
})
|
|
130
139
|
]
|
|
131
140
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
133
|
-
open:
|
|
134
|
-
onOpenChange:
|
|
141
|
+
/* @__PURE__ */ h(u, {
|
|
142
|
+
open: T,
|
|
143
|
+
onOpenChange: E
|
|
135
144
|
})
|
|
136
145
|
]
|
|
137
146
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventsPage.js","names":[],"sources":["../../../../src/comradecircle/pages/events/EventsPage.tsx"],"sourcesContent":["/**\n * EventsPage — every event across the circles the member belongs to.\n * @module comradecircle/pages/events\n *\n * Reads `comListEvents` (all circles, newest-scheduled first) with a lifecycle\n * status filter (scheduled → live → completed → cancelled). Scheduling a new\n * event opens the shared {@link CreateEventDialog} without a fixed circle, so it\n * shows its own active-circle picker.\n */\n\nimport { useState } from 'react';\nimport { CalendarDays, Plus } from 'lucide-react';\nimport { Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { EventCard } from '../../components/EventCard';\nimport { CreateEventDialog } from '../../components/CreateEventDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useComEvents, useCursorPager } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatNumber } from '../../utils/formatters';\nimport type { ComEventStatus } from '../../types';\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = 'ALL' | ComEventStatus;\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LIVE', label: 'Live' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\n/** One placeholder tile that mirrors {@link EventCard}'s glass + section rhythm. */\nfunction EventCardSkeleton() {\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col p-5\" aria-hidden=\"true\">\n <div className=\"flex items-start justify-between gap-3\">\n <Skeleton className=\"h-5 w-20 rounded-full\" />\n <Skeleton className=\"h-4 w-14\" />\n </div>\n <Skeleton className=\"mt-2 h-5 w-3/5\" />\n <Skeleton className=\"mt-2 h-3.5 w-full\" />\n <Skeleton className=\"mt-1.5 h-3.5 w-4/5\" />\n <div className=\"mt-3 flex flex-col gap-2\">\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-4 w-32\" />\n </div>\n <Skeleton className=\"mt-3 h-1 w-full rounded-full\" />\n <div className=\"mt-4 flex items-center justify-end gap-2 border-t border-border-seam pt-3\">\n <Skeleton className=\"h-8 w-20 rounded-md\" />\n <Skeleton className=\"h-8 w-24 rounded-md\" />\n </div>\n </GlassCard>\n );\n}\n\n/** Skeleton grid mirroring the live `md:grid-cols-2` EventCard layout. */\nfunction EventsSkeletonGrid() {\n return (\n <div className=\"grid gap-4 md:grid-cols-2\" aria-hidden=\"true\">\n {Array.from({ length: 4 }, (_, i) => (\n <EventCardSkeleton key={i} />\n ))}\n </div>\n );\n}\n\nexport function EventsPage() {\n const { navigateTo } = useComradeCircle();\n const pager = useCursorPager();\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const { data, isLoading, isError, refetch } = useComEvents({\n status: status === 'ALL' ? undefined : status,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const events = data?.items ?? [];\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const filterLabel = status === 'ALL' ? '' : ` ${status.toLowerCase()}`;\n const resultCaption =\n `${formatNumber(events.length)}${filterLabel} ` +\n `${events.length === 1 ? 'event' : 'events'}` +\n (pager.page > 0 ? ` · page ${pager.page + 1}` : hasMore ? ' · more available' : '');\n\n return (\n <PageLayout\n title=\"Events\"\n description=\"Upcoming and past gatherings across all the circles you belong to.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n }\n >\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={changeStatus}\n className=\"mb-5\"\n />\n\n {isLoading ? (\n <EventsSkeletonGrid />\n ) : isError ? (\n <ErrorView message=\"We couldn't load events.\" onRetry={() => refetch()} />\n ) : events.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title={status === 'ALL' ? 'No events yet' : 'No events match this filter'}\n description={\n status === 'ALL'\n ? 'Schedule a gathering for one of your circles — online, in person, or hybrid.'\n : 'Try a different status, or schedule a new gathering for one of your circles.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New event\n </Button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n <p className=\"text-xs font-medium text-text-tertiary\">{resultCaption}</p>\n <div className=\"grid gap-4 md:grid-cols-2\">\n {events.map((event) => (\n <EventCard\n key={event.id}\n event={event}\n onOpen={() => navigateTo('events/' + event.id)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={events.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n\n <CreateEventDialog open={createOpen} onOpenChange={setCreateOpen} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EventsPage.js","names":[],"sources":["../../../../src/comradecircle/pages/events/EventsPage.tsx"],"sourcesContent":["/**\n * EventsPage — every event across the circles the member belongs to.\n * @module comradecircle/pages/events\n *\n * Reads `comListEvents` (all circles, newest-scheduled first) with a lifecycle\n * status filter (scheduled → live → completed → cancelled). Scheduling a new\n * event opens the shared {@link CreateEventDialog} without a fixed circle, so it\n * shows its own active-circle picker.\n */\n\nimport { useState } from 'react';\nimport { CalendarDays, Plus } from 'lucide-react';\nimport { Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { EventCard } from '../../components/EventCard';\nimport { CreateEventDialog } from '../../components/CreateEventDialog';\nimport { EmptyState } from '../../components/EmptyState';\nimport { ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useComEvents, useCursorPager, useTr } from '../../hooks';\nimport { useComradeCircle } from '../../context';\nimport { formatNumber } from '../../utils/formatters';\nimport type { ComEventStatus } from '../../types';\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = 'ALL' | ComEventStatus;\n\n/** One placeholder tile that mirrors {@link EventCard}'s glass + section rhythm. */\nfunction EventCardSkeleton() {\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col p-5\" aria-hidden=\"true\">\n <div className=\"flex items-start justify-between gap-3\">\n <Skeleton className=\"h-5 w-20 rounded-full\" />\n <Skeleton className=\"h-4 w-14\" />\n </div>\n <Skeleton className=\"mt-2 h-5 w-3/5\" />\n <Skeleton className=\"mt-2 h-3.5 w-full\" />\n <Skeleton className=\"mt-1.5 h-3.5 w-4/5\" />\n <div className=\"mt-3 flex flex-col gap-2\">\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-4 w-32\" />\n </div>\n <Skeleton className=\"mt-3 h-1 w-full rounded-full\" />\n <div className=\"mt-4 flex items-center justify-end gap-2 border-t border-border-seam pt-3\">\n <Skeleton className=\"h-8 w-20 rounded-md\" />\n <Skeleton className=\"h-8 w-24 rounded-md\" />\n </div>\n </GlassCard>\n );\n}\n\n/** Skeleton grid mirroring the live `md:grid-cols-2` EventCard layout. */\nfunction EventsSkeletonGrid() {\n return (\n <div className=\"grid gap-4 md:grid-cols-2\" aria-hidden=\"true\">\n {Array.from({ length: 4 }, (_, i) => (\n <EventCardSkeleton key={i} />\n ))}\n </div>\n );\n}\n\nexport function EventsPage() {\n const { navigateTo } = useComradeCircle();\n const pager = useCursorPager();\n const tr = useTr();\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: tr('comradecircle.pages.events.filterAll', 'All') },\n { value: 'SCHEDULED', label: tr('comradecircle.pages.events.filterScheduled', 'Scheduled') },\n { value: 'LIVE', label: tr('comradecircle.pages.events.filterLive', 'Live') },\n { value: 'COMPLETED', label: tr('comradecircle.pages.events.filterCompleted', 'Completed') },\n { value: 'CANCELLED', label: tr('comradecircle.pages.events.filterCancelled', 'Cancelled') },\n ];\n\n const { data, isLoading, isError, refetch } = useComEvents({\n status: status === 'ALL' ? undefined : status,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const events = data?.items ?? [];\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const resultCaption =\n `${formatNumber(events.length)} ` +\n tr(\n events.length === 1\n ? 'comradecircle.pages.events.eventCount'\n : 'comradecircle.pages.events.eventsCount',\n events.length === 1 ? 'event' : 'events'\n ) +\n (pager.page > 0\n ? ` · ${tr('comradecircle.pages.events.pageSuffix', 'page {{page}}', { page: pager.page + 1 })}`\n : hasMore\n ? ` · ${tr('comradecircle.pages.events.moreAvailable', 'more available')}`\n : '');\n\n return (\n <PageLayout\n title={tr('comradecircle.pages.events.title', 'Events')}\n description={tr(\n 'comradecircle.pages.events.description',\n 'Upcoming and past gatherings across all the circles you belong to.'\n )}\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />{' '}\n {tr('comradecircle.pages.events.newEvent', 'New event')}\n </Button>\n }\n >\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={changeStatus}\n className=\"mb-5\"\n />\n\n {isLoading ? (\n <EventsSkeletonGrid />\n ) : isError ? (\n <ErrorView\n message={tr('comradecircle.pages.events.errorMessage', \"We couldn't load events.\")}\n onRetry={() => refetch()}\n />\n ) : events.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title={\n status === 'ALL'\n ? tr('comradecircle.pages.events.emptyTitleAll', 'No events yet')\n : tr('comradecircle.pages.events.emptyTitleFiltered', 'No events match this filter')\n }\n description={\n status === 'ALL'\n ? tr(\n 'comradecircle.pages.events.emptyDescriptionAll',\n 'Schedule a gathering for one of your circles — online, in person, or hybrid.'\n )\n : tr(\n 'comradecircle.pages.events.emptyDescriptionFiltered',\n 'Try a different status, or schedule a new gathering for one of your circles.'\n )\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />{' '}\n {tr('comradecircle.pages.events.newEvent', 'New event')}\n </Button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n <p className=\"text-xs font-medium text-text-tertiary\">{resultCaption}</p>\n <div className=\"grid gap-4 md:grid-cols-2\">\n {events.map((event) => (\n <EventCard\n key={event.id}\n event={event}\n onOpen={() => navigateTo('events/' + event.id)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={events.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </div>\n )}\n\n <CreateEventDialog open={createOpen} onOpenChange={setCreateOpen} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MemberProfilePage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/members/MemberProfilePage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"MemberProfilePage.d.ts","sourceRoot":"","sources":["../../../../src/comradecircle/pages/members/MemberProfilePage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA2PH,wBAAgB,iBAAiB,4CA8LhC"}
|