@burdenoff/microfe-comradecircle 2026.525.1 → 2026.704.2

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 (107) hide show
  1. package/dist/comradecircle/ComradeCircleRoot.js +75 -95
  2. package/dist/comradecircle/ComradeCircleRoot.js.map +1 -1
  3. package/dist/comradecircle/ComradeCircleRoutes.js +58 -37
  4. package/dist/comradecircle/ComradeCircleRoutes.js.map +1 -1
  5. package/dist/comradecircle/components/Avatar.js +47 -0
  6. package/dist/comradecircle/components/Avatar.js.map +1 -0
  7. package/dist/comradecircle/components/AvatarUploadField.js +80 -0
  8. package/dist/comradecircle/components/AvatarUploadField.js.map +1 -0
  9. package/dist/comradecircle/components/CircleCard.js +118 -0
  10. package/dist/comradecircle/components/CircleCard.js.map +1 -0
  11. package/dist/comradecircle/components/CommentThread.js +105 -0
  12. package/dist/comradecircle/components/CommentThread.js.map +1 -0
  13. package/dist/comradecircle/components/CreateCircleDialog.js +159 -0
  14. package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -0
  15. package/dist/comradecircle/components/CreateEventDialog.js +210 -0
  16. package/dist/comradecircle/components/CreateEventDialog.js.map +1 -0
  17. package/dist/comradecircle/components/EmptyState.js +35 -0
  18. package/dist/comradecircle/components/EmptyState.js.map +1 -0
  19. package/dist/comradecircle/components/EventCard.js +93 -0
  20. package/dist/comradecircle/components/EventCard.js.map +1 -0
  21. package/dist/comradecircle/components/FileImage.js +20 -0
  22. package/dist/comradecircle/components/FileImage.js.map +1 -0
  23. package/dist/comradecircle/components/FilterTabs.js +27 -0
  24. package/dist/comradecircle/components/FilterTabs.js.map +1 -0
  25. package/dist/comradecircle/components/ImageAttachments.js +97 -0
  26. package/dist/comradecircle/components/ImageAttachments.js.map +1 -0
  27. package/dist/comradecircle/components/MemberIdentity.js +44 -0
  28. package/dist/comradecircle/components/MemberIdentity.js.map +1 -0
  29. package/dist/comradecircle/components/MiniBar.js +22 -0
  30. package/dist/comradecircle/components/MiniBar.js.map +1 -0
  31. package/dist/comradecircle/components/PageLayout.js +38 -0
  32. package/dist/comradecircle/components/PageLayout.js.map +1 -0
  33. package/dist/comradecircle/components/Pagination.js +50 -0
  34. package/dist/comradecircle/components/Pagination.js.map +1 -0
  35. package/dist/comradecircle/components/PollCard.js +97 -0
  36. package/dist/comradecircle/components/PollCard.js.map +1 -0
  37. package/dist/comradecircle/components/PostCard.js +163 -0
  38. package/dist/comradecircle/components/PostCard.js.map +1 -0
  39. package/dist/comradecircle/components/PostComposer.js +87 -0
  40. package/dist/comradecircle/components/PostComposer.js.map +1 -0
  41. package/dist/comradecircle/components/ReactionBar.js +122 -0
  42. package/dist/comradecircle/components/ReactionBar.js.map +1 -0
  43. package/dist/comradecircle/components/StateViews.js +49 -0
  44. package/dist/comradecircle/components/StateViews.js.map +1 -0
  45. package/dist/comradecircle/components/StatusBadge.js +68 -0
  46. package/dist/comradecircle/components/StatusBadge.js.map +1 -0
  47. package/dist/comradecircle/components/Tabs.js +31 -0
  48. package/dist/comradecircle/components/Tabs.js.map +1 -0
  49. package/dist/comradecircle/components/TagInput.js +49 -0
  50. package/dist/comradecircle/components/TagInput.js.map +1 -0
  51. package/dist/comradecircle/context/ComradeCircleContext.js +85 -0
  52. package/dist/comradecircle/context/ComradeCircleContext.js.map +1 -0
  53. package/dist/comradecircle/context/index.js +1 -0
  54. package/dist/comradecircle/hooks/index.js +3 -0
  55. package/dist/comradecircle/hooks/useComradeCircleApi.js +419 -0
  56. package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -0
  57. package/dist/comradecircle/hooks/useCursorPager.js +25 -0
  58. package/dist/comradecircle/hooks/useCursorPager.js.map +1 -0
  59. package/dist/comradecircle/hooks/useFiles.js +78 -0
  60. package/dist/comradecircle/hooks/useFiles.js.map +1 -0
  61. package/dist/comradecircle/index.js +5 -2
  62. package/dist/comradecircle/lib/graphql-cache.js +9 -0
  63. package/dist/comradecircle/lib/graphql-cache.js.map +1 -0
  64. package/dist/comradecircle/lib/toast.js +39 -0
  65. package/dist/comradecircle/lib/toast.js.map +1 -0
  66. package/dist/comradecircle/pages/FeedPage.js +65 -0
  67. package/dist/comradecircle/pages/FeedPage.js.map +1 -0
  68. package/dist/comradecircle/pages/circles/CircleDetailPage.js +651 -0
  69. package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -0
  70. package/dist/comradecircle/pages/circles/CirclesPage.js +129 -0
  71. package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -0
  72. package/dist/comradecircle/pages/events/EventDetailPage.js +276 -0
  73. package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -0
  74. package/dist/comradecircle/pages/events/EventsPage.js +102 -0
  75. package/dist/comradecircle/pages/events/EventsPage.js.map +1 -0
  76. package/dist/comradecircle/pages/members/MemberProfilePage.js +138 -0
  77. package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -0
  78. package/dist/comradecircle/pages/members/MembersPage.js +153 -0
  79. package/dist/comradecircle/pages/members/MembersPage.js.map +1 -0
  80. package/dist/comradecircle/pages/profile/MyProfilePage.js +266 -0
  81. package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -0
  82. package/dist/comradecircle/utils/cn.js +10 -0
  83. package/dist/comradecircle/utils/cn.js.map +1 -0
  84. package/dist/comradecircle/utils/formatters.js +51 -0
  85. package/dist/comradecircle/utils/formatters.js.map +1 -0
  86. package/dist/comradecircle/utils/navigation.js +2 -1
  87. package/dist/comradecircle/utils/navigation.js.map +1 -1
  88. package/dist/comradecircle/utils/slug.js +8 -0
  89. package/dist/comradecircle/utils/slug.js.map +1 -0
  90. package/dist/index.js +6 -4
  91. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  92. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  93. package/dist/node_modules/sonner/dist/index.js +163 -0
  94. package/dist/node_modules/sonner/dist/index.js.map +1 -0
  95. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  96. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  97. package/package.json +3 -2
  98. package/dist/comradecircle/pages/CommunitiesPage.js +0 -299
  99. package/dist/comradecircle/pages/CommunitiesPage.js.map +0 -1
  100. package/dist/comradecircle/pages/CreatorPage.js +0 -399
  101. package/dist/comradecircle/pages/CreatorPage.js.map +0 -1
  102. package/dist/comradecircle/pages/DashboardPage.js +0 -265
  103. package/dist/comradecircle/pages/DashboardPage.js.map +0 -1
  104. package/dist/comradecircle/providers/ComradeCircleProvider.js +0 -27
  105. package/dist/comradecircle/providers/ComradeCircleProvider.js.map +0 -1
  106. package/dist/comradecircle/store/comradecircleStore.js +0 -345
  107. package/dist/comradecircle/store/comradecircleStore.js.map +0 -1
@@ -0,0 +1,93 @@
1
+ import { useComRsvpEvent as e } from "../hooks/useComradeCircleApi.js";
2
+ import "../hooks/index.js";
3
+ import { describeError as t, notify as n } from "../lib/toast.js";
4
+ import { formatDateTime as r, formatNumber as i } from "../utils/formatters.js";
5
+ import { StatusBadge as a } from "./StatusBadge.js";
6
+ import { Calendar as o, ExternalLink as s, MapPin as c, Users as l, Video as u } from "lucide-react";
7
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
+ import { Button as p, GlassCard as m } from "@burdenoff/fe-libs/ui";
9
+ //#region src/comradecircle/components/EventCard.tsx
10
+ var h = [{
11
+ status: "GOING",
12
+ label: "Going"
13
+ }, {
14
+ status: "INTERESTED",
15
+ label: "Interested"
16
+ }];
17
+ function g({ event: g, onOpen: _ }) {
18
+ let v = e(), y = g.status === "SCHEDULED" || g.status === "LIVE", b = async (e) => {
19
+ try {
20
+ await v.mutateAsync({
21
+ eventId: g.id,
22
+ status: e
23
+ }), n.success(`RSVP: ${e === "GOING" ? "Going" : "Interested"}`, g.title);
24
+ } catch (e) {
25
+ n.error("Could not RSVP", t(e));
26
+ }
27
+ }, x = g.mode === "IN_PERSON" ? c : u;
28
+ return /* @__PURE__ */ f(m, {
29
+ treatment: "plain",
30
+ className: "flex flex-col p-5",
31
+ children: [
32
+ /* @__PURE__ */ f("div", {
33
+ className: "flex items-start justify-between gap-3",
34
+ children: [/* @__PURE__ */ d("button", {
35
+ type: "button",
36
+ onClick: _,
37
+ className: "text-left text-base font-semibold text-text-primary hover:underline",
38
+ children: g.title
39
+ }), /* @__PURE__ */ d(a, { status: g.status })]
40
+ }),
41
+ g.description && /* @__PURE__ */ d("p", {
42
+ className: "mt-2 line-clamp-2 text-sm text-text-secondary",
43
+ children: g.description
44
+ }),
45
+ /* @__PURE__ */ f("div", {
46
+ className: "mt-3 flex flex-col gap-1.5 text-sm text-text-secondary",
47
+ children: [/* @__PURE__ */ f("span", {
48
+ className: "inline-flex items-center gap-2",
49
+ children: [
50
+ /* @__PURE__ */ d(o, { className: "h-4 w-4 text-text-tertiary" }),
51
+ " ",
52
+ r(g.startsAt)
53
+ ]
54
+ }), /* @__PURE__ */ f("span", {
55
+ className: "inline-flex items-center gap-2",
56
+ children: [/* @__PURE__ */ d(x, { className: "h-4 w-4 text-text-tertiary" }), g.mode === "IN_PERSON" ? g.location || "In person" : g.mode === "HYBRID" ? `Hybrid${g.location ? ` · ${g.location}` : ""}` : "Online"]
57
+ })]
58
+ }),
59
+ /* @__PURE__ */ f("div", {
60
+ className: "mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3",
61
+ children: [/* @__PURE__ */ f("span", {
62
+ className: "inline-flex items-center gap-1 text-xs text-text-tertiary",
63
+ children: [
64
+ /* @__PURE__ */ d(l, { className: "h-3.5 w-3.5" }),
65
+ " ",
66
+ i(g.rsvpCount),
67
+ g.capacity ? ` / ${i(g.capacity)}` : "",
68
+ " attending"
69
+ ]
70
+ }), /* @__PURE__ */ f("div", {
71
+ className: "flex items-center gap-2",
72
+ children: [g.status === "LIVE" && g.joinUrl && /* @__PURE__ */ f("a", {
73
+ href: g.joinUrl,
74
+ target: "_blank",
75
+ rel: "noreferrer noopener",
76
+ className: "inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline",
77
+ children: ["Join ", /* @__PURE__ */ d(s, { className: "h-3 w-3" })]
78
+ }), y && h.map((e) => /* @__PURE__ */ d(p, {
79
+ size: "sm",
80
+ variant: e.status === "GOING" ? "default" : "outline",
81
+ onClick: () => b(e.status),
82
+ disabled: v.isPending,
83
+ children: e.label
84
+ }, e.status))]
85
+ })]
86
+ })
87
+ ]
88
+ });
89
+ }
90
+ //#endregion
91
+ export { g as EventCard };
92
+
93
+ //# sourceMappingURL=EventCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventCard.js","names":[],"sources":["../../../src/comradecircle/components/EventCard.tsx"],"sourcesContent":["/**\n * EventCard — a tile for a community event with a quick RSVP control.\n * @module comradecircle/components\n *\n * Writes `comRsvpEvent`. Shows mode + lifecycle status, schedule, attendee\n * count, and (for online/hybrid) a join link when the event is live.\n */\n\nimport { Calendar, MapPin, Video, Users, ExternalLink } from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { useComRsvpEvent } from '../hooks';\nimport { formatDateTime, formatNumber } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComEvent, ComRsvpStatus } from '../types';\n\ninterface EventCardProps {\n event: ComEvent;\n onOpen: () => void;\n}\n\nconst RSVP_ACTIONS: { status: ComRsvpStatus; label: string }[] = [\n { status: 'GOING', label: 'Going' },\n { status: 'INTERESTED', label: 'Interested' },\n];\n\nexport function EventCard({ event, onOpen }: EventCardProps) {\n const rsvp = useComRsvpEvent();\n const canRsvp = event.status === 'SCHEDULED' || event.status === 'LIVE';\n\n const doRsvp = async (status: ComRsvpStatus) => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status });\n notify.success(`RSVP: ${status === 'GOING' ? 'Going' : 'Interested'}`, event.title);\n } catch (err) {\n notify.error('Could not RSVP', describeError(err));\n }\n };\n\n const ModeIcon = event.mode === 'IN_PERSON' ? MapPin : Video;\n\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"text-left text-base font-semibold text-text-primary hover:underline\"\n >\n {event.title}\n </button>\n <StatusBadge status={event.status} />\n </div>\n\n {event.description && (\n <p className=\"mt-2 line-clamp-2 text-sm text-text-secondary\">{event.description}</p>\n )}\n\n <div className=\"mt-3 flex flex-col gap-1.5 text-sm text-text-secondary\">\n <span className=\"inline-flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-text-tertiary\" /> {formatDateTime(event.startsAt)}\n </span>\n <span className=\"inline-flex items-center gap-2\">\n <ModeIcon className=\"h-4 w-4 text-text-tertiary\" />\n {event.mode === 'IN_PERSON'\n ? event.location || 'In person'\n : event.mode === 'HYBRID'\n ? `Hybrid${event.location ? ` · ${event.location}` : ''}`\n : 'Online'}\n </span>\n </div>\n\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <span className=\"inline-flex items-center gap-1 text-xs text-text-tertiary\">\n <Users className=\"h-3.5 w-3.5\" /> {formatNumber(event.rsvpCount)}\n {event.capacity ? ` / ${formatNumber(event.capacity)}` : ''} attending\n </span>\n <div className=\"flex items-center gap-2\">\n {event.status === 'LIVE' && event.joinUrl && (\n <a\n href={event.joinUrl}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline\"\n >\n Join <ExternalLink className=\"h-3 w-3\" />\n </a>\n )}\n {canRsvp &&\n RSVP_ACTIONS.map((a) => (\n <Button\n key={a.status}\n size=\"sm\"\n variant={a.status === 'GOING' ? 'default' : 'outline'}\n onClick={() => doRsvp(a.status)}\n disabled={rsvp.isPending}\n >\n {a.label}\n </Button>\n ))}\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;AAqBA,IAAM,IAA2D,CAC/D;CAAE,QAAQ;CAAS,OAAO;CAAS,EACnC;CAAE,QAAQ;CAAc,OAAO;CAAc,CAC9C;AAED,SAAgB,EAAU,EAAE,UAAO,aAA0B;CAC3D,IAAM,IAAO,GAAiB,EACxB,IAAU,EAAM,WAAW,eAAe,EAAM,WAAW,QAE3D,IAAS,OAAO,MAA0B;AAC9C,MAAI;AAEF,GADA,MAAM,EAAK,YAAY;IAAE,SAAS,EAAM;IAAI;IAAQ,CAAC,EACrD,EAAO,QAAQ,SAAS,MAAW,UAAU,UAAU,gBAAgB,EAAM,MAAM;WAC5E,GAAK;AACZ,KAAO,MAAM,kBAAkB,EAAc,EAAI,CAAC;;IAIhD,IAAW,EAAM,SAAS,cAAc,IAAS;AAEvD,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAM;KACA,CAAA,EACT,kBAAC,GAAD,EAAa,QAAQ,EAAM,QAAU,CAAA,CACjC;;GAEL,EAAM,eACL,kBAAC,KAAD;IAAG,WAAU;cAAiD,EAAM;IAAgB,CAAA;GAGtF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACE,kBAAC,GAAD,EAAU,WAAU,8BAA+B,CAAA;;MAAE,EAAe,EAAM,SAAS;MAC9E;QACP,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAU,WAAU,8BAA+B,CAAA,EAClD,EAAM,SAAS,cACZ,EAAM,YAAY,cAClB,EAAM,SAAS,WACb,SAAS,EAAM,WAAW,MAAM,EAAM,aAAa,OACnD,SACD;OACH;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA;;MAAE,EAAa,EAAM,UAAU;MAC/D,EAAM,WAAW,MAAM,EAAa,EAAM,SAAS,KAAK;MAAG;MACvD;QACP,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAM,WAAW,UAAU,EAAM,WAChC,kBAAC,KAAD;MACE,MAAM,EAAM;MACZ,QAAO;MACP,KAAI;MACJ,WAAU;gBAJZ,CAKC,SACM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CACvC;SAEL,KACC,EAAa,KAAK,MAChB,kBAAC,GAAD;MAEE,MAAK;MACL,SAAS,EAAE,WAAW,UAAU,YAAY;MAC5C,eAAe,EAAO,EAAE,OAAO;MAC/B,UAAU,EAAK;gBAEd,EAAE;MACI,EAPF,EAAE,OAOA,CACT,CACA;OACF;;GACI"}
@@ -0,0 +1,20 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { useFileUrl as t } from "../hooks/useFiles.js";
3
+ import { useState as n } from "react";
4
+ import { Fragment as r, jsx as i } from "react/jsx-runtime";
5
+ import { Skeleton as a } from "@burdenoff/fe-libs/ui";
6
+ //#region src/comradecircle/components/FileImage.tsx
7
+ function o({ fileId: o, alt: s, className: c, fallback: l = null }) {
8
+ let { data: u, isLoading: d, isError: f } = t(o), [p, m] = n(!1);
9
+ return o ? d ? /* @__PURE__ */ i(a, { className: e("h-full w-full", c) }) : f || p || !u ? /* @__PURE__ */ i(r, { children: l }) : /* @__PURE__ */ i("img", {
10
+ src: u,
11
+ alt: s,
12
+ loading: "lazy",
13
+ onError: () => m(!0),
14
+ className: e("object-cover", c)
15
+ }) : /* @__PURE__ */ i(r, { children: l });
16
+ }
17
+ //#endregion
18
+ export { o as FileImage };
19
+
20
+ //# sourceMappingURL=FileImage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileImage.js","names":[],"sources":["../../../src/comradecircle/components/FileImage.tsx"],"sourcesContent":["/**\n * FileImage — render a wspace-files-svc file id as an image.\n * @module comradecircle/components\n *\n * Resolves the id to a presigned URL via {@link useFileUrl}. Shows a skeleton\n * while resolving and renders nothing (so the caller's fallback shows) if the id\n * is absent or the URL fails to resolve.\n */\n\nimport { useState } from 'react';\nimport { Skeleton } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useFileUrl } from '../hooks/useFiles';\n\ninterface FileImageProps {\n fileId?: string | null;\n alt: string;\n className?: string;\n /** Rendered when there is no fileId, resolution fails, or the image errors. */\n fallback?: React.ReactNode;\n}\n\nexport function FileImage({ fileId, alt, className, fallback = null }: FileImageProps) {\n const { data: url, isLoading, isError } = useFileUrl(fileId);\n const [imgError, setImgError] = useState(false);\n\n if (!fileId) return <>{fallback}</>;\n if (isLoading) return <Skeleton className={cn('h-full w-full', className)} />;\n if (isError || imgError || !url) return <>{fallback}</>;\n\n return (\n <img\n src={url}\n alt={alt}\n loading=\"lazy\"\n onError={() => setImgError(true)}\n className={cn('object-cover', className)}\n />\n );\n}\n"],"mappings":";;;;;;AAsBA,SAAgB,EAAU,EAAE,WAAQ,QAAK,cAAW,cAAW,QAAwB;CACrF,IAAM,EAAE,MAAM,GAAK,cAAW,eAAY,EAAW,EAAO,EACtD,CAAC,GAAU,KAAe,EAAS,GAAM;AAM/C,QAJK,IACD,IAAkB,kBAAC,GAAD,EAAU,WAAW,EAAG,iBAAiB,EAAU,EAAI,CAAA,GACzE,KAAW,KAAY,CAAC,IAAY,kBAAA,GAAA,EAAA,UAAG,GAAY,CAAA,GAGrD,kBAAC,OAAD;EACE,KAAK;EACA;EACL,SAAQ;EACR,eAAe,EAAY,GAAK;EAChC,WAAW,EAAG,gBAAgB,EAAU;EACxC,CAAA,GAXgB,kBAAA,GAAA,EAAA,UAAG,GAAY,CAAA"}
@@ -0,0 +1,27 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ //#region src/comradecircle/components/FilterTabs.tsx
4
+ function r({ options: r, value: i, onChange: a, className: o }) {
5
+ return /* @__PURE__ */ t("div", {
6
+ role: "tablist",
7
+ className: e("inline-flex flex-wrap items-center gap-1 rounded-xl border border-border-seam bg-bg-surface p-1", o),
8
+ children: r.map((r) => {
9
+ let o = r.value === i;
10
+ return /* @__PURE__ */ n("button", {
11
+ type: "button",
12
+ role: "tab",
13
+ "aria-selected": o,
14
+ onClick: () => a(r.value),
15
+ className: e("rounded-lg px-3 py-1.5 text-sm font-medium transition-colors", o ? "bg-action-primary-bg text-action-primary-text" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
16
+ children: [r.label, typeof r.count == "number" && /* @__PURE__ */ t("span", {
17
+ className: e("ml-1.5 rounded-full px-1.5 py-0.5 text-xs tabular-nums", o ? "bg-black/10 dark:bg-white/15" : "bg-bg-sunken"),
18
+ children: r.count
19
+ })]
20
+ }, r.value);
21
+ })
22
+ });
23
+ }
24
+ //#endregion
25
+ export { r as FilterTabs };
26
+
27
+ //# sourceMappingURL=FilterTabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterTabs.js","names":[],"sources":["../../../src/comradecircle/components/FilterTabs.tsx"],"sourcesContent":["/**\n * FilterTabs — a segmented status/enum filter control.\n * @module comradecircle/components\n */\n\nimport { cn } from '../utils/cn';\n\nexport interface FilterOption<V extends string> {\n value: V;\n label: string;\n count?: number;\n}\n\ninterface FilterTabsProps<V extends string> {\n options: FilterOption<V>[];\n value: V;\n onChange: (value: V) => void;\n className?: string;\n}\n\nexport function FilterTabs<V extends string>({\n options,\n value,\n onChange,\n className,\n}: FilterTabsProps<V>) {\n return (\n <div\n role=\"tablist\"\n className={cn(\n 'inline-flex flex-wrap items-center gap-1 rounded-xl border border-border-seam bg-bg-surface p-1',\n className\n )}\n >\n {options.map((opt) => {\n const active = opt.value === value;\n return (\n <button\n key={opt.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n onClick={() => onChange(opt.value)}\n className={cn(\n 'rounded-lg px-3 py-1.5 text-sm font-medium transition-colors',\n active\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {opt.label}\n {typeof opt.count === 'number' && (\n <span\n className={cn(\n 'ml-1.5 rounded-full px-1.5 py-0.5 text-xs tabular-nums',\n active ? 'bg-black/10 dark:bg-white/15' : 'bg-bg-sunken'\n )}\n >\n {opt.count}\n </span>\n )}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;AAoBA,SAAgB,EAA6B,EAC3C,YACA,UACA,aACA,gBACqB;AACrB,QACE,kBAAC,OAAD;EACE,MAAK;EACL,WAAW,EACT,mGACA,EACD;YAEA,EAAQ,KAAK,MAAQ;GACpB,IAAM,IAAS,EAAI,UAAU;AAC7B,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,MAAK;IACL,iBAAe;IACf,eAAe,EAAS,EAAI,MAAM;IAClC,WAAW,EACT,gEACA,IACI,kDACA,iEACL;cAXH,CAaG,EAAI,OACJ,OAAO,EAAI,SAAU,YACpB,kBAAC,QAAD;KACE,WAAW,EACT,0DACA,IAAS,iCAAiC,eAC3C;eAEA,EAAI;KACA,CAAA,CAEF;MAvBF,EAAI,MAuBF;IAEX;EACE,CAAA"}
@@ -0,0 +1,97 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { FILE_FOLDERS as t, uploadImageToFiles as n } from "../hooks/useFiles.js";
3
+ import { FileImage as r } from "./FileImage.js";
4
+ import { describeError as i, notify as a } from "../lib/toast.js";
5
+ import { useRef as o, useState as s } from "react";
6
+ import { ImagePlus as c, Loader2 as l, X as u } from "lucide-react";
7
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
+ //#region src/comradecircle/components/ImageAttachments.tsx
9
+ var p = 10 * 1024 * 1024, m = 6;
10
+ function h({ fileIds: e, onChange: h, disabled: g = !1 }) {
11
+ let _ = o(null), [v, y] = s(!1), [b, x] = s({}), S = async (r) => {
12
+ let o = Array.from(r.target.files ?? []);
13
+ if (r.target.value = "", o.length === 0) return;
14
+ let s = m - e.length;
15
+ if (s <= 0) {
16
+ a.warning("Attachment limit reached", `Up to ${m} images per post.`);
17
+ return;
18
+ }
19
+ y(!0);
20
+ try {
21
+ let r = [], i = {};
22
+ for (let e of o.slice(0, s)) {
23
+ if (!e.type.startsWith("image/")) continue;
24
+ if (e.size > p) {
25
+ a.error("Image too large", `${e.name} exceeds 10 MB.`);
26
+ continue;
27
+ }
28
+ let o = await n(e, t.posts);
29
+ r.push(o.id), i[o.id] = o.url;
30
+ }
31
+ r.length && (x((e) => ({
32
+ ...e,
33
+ ...i
34
+ })), h([...e, ...r]));
35
+ } catch (e) {
36
+ a.error("Upload failed", i(e));
37
+ } finally {
38
+ y(!1);
39
+ }
40
+ }, C = (t) => h(e.filter((e) => e !== t));
41
+ return /* @__PURE__ */ f("div", { children: [
42
+ e.length > 0 && /* @__PURE__ */ d("div", {
43
+ className: "mb-2 flex flex-wrap gap-2",
44
+ children: e.map((e) => /* @__PURE__ */ f("div", {
45
+ className: "relative h-20 w-20 overflow-hidden rounded-lg ring-1 ring-border-seam",
46
+ children: [b[e] ? /* @__PURE__ */ d("img", {
47
+ src: b[e],
48
+ alt: "attachment",
49
+ className: "h-full w-full object-cover"
50
+ }) : /* @__PURE__ */ d(r, {
51
+ fileId: e,
52
+ alt: "attachment",
53
+ className: "h-full w-full"
54
+ }), /* @__PURE__ */ d("button", {
55
+ type: "button",
56
+ onClick: () => C(e),
57
+ "aria-label": "Remove attachment",
58
+ className: "absolute right-0.5 top-0.5 rounded-full bg-bg-canvas/70 p-0.5 text-text-primary hover:bg-bg-canvas",
59
+ children: /* @__PURE__ */ d(u, { className: "h-3.5 w-3.5" })
60
+ })]
61
+ }, e))
62
+ }),
63
+ /* @__PURE__ */ d("input", {
64
+ ref: _,
65
+ type: "file",
66
+ accept: "image/*",
67
+ multiple: !0,
68
+ className: "sr-only",
69
+ onChange: S,
70
+ disabled: g || v
71
+ }),
72
+ /* @__PURE__ */ f("button", {
73
+ type: "button",
74
+ onClick: () => _.current?.click(),
75
+ disabled: g || v || e.length >= m,
76
+ className: "inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary transition-colors hover:text-text-primary disabled:opacity-50",
77
+ children: [v ? /* @__PURE__ */ d(l, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ d(c, { className: "h-4 w-4" }), v ? "Uploading…" : "Add images"]
78
+ })
79
+ ] });
80
+ }
81
+ function g({ fileIds: t, className: n }) {
82
+ return t.length === 0 ? null : /* @__PURE__ */ d("div", {
83
+ className: e("mt-3 grid gap-2", t.length === 1 ? "grid-cols-1" : "grid-cols-2 sm:grid-cols-3", n),
84
+ children: t.map((n) => /* @__PURE__ */ d("div", {
85
+ className: e("overflow-hidden rounded-xl ring-1 ring-border-seam", t.length === 1 ? "max-h-96" : "aspect-square"),
86
+ children: /* @__PURE__ */ d(r, {
87
+ fileId: n,
88
+ alt: "Post attachment",
89
+ className: "h-full w-full"
90
+ })
91
+ }, n))
92
+ });
93
+ }
94
+ //#endregion
95
+ export { g as AttachmentGrid, h as AttachmentPicker };
96
+
97
+ //# sourceMappingURL=ImageAttachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageAttachments.js","names":[],"sources":["../../../src/comradecircle/components/ImageAttachments.tsx"],"sourcesContent":["/**\n * ImageAttachments — attach images to a post + render a post's attachments.\n * @module comradecircle/components\n *\n * Uploads go to wspace-files-svc (reuse, no blob storage here); the composer\n * keeps only file ids, which are persisted as ComPost.attachmentFileIds.\n */\n\nimport { useRef, useState } from 'react';\nimport { ImagePlus, Loader2, X } from 'lucide-react';\nimport { cn } from '../utils/cn';\nimport { FileImage } from './FileImage';\nimport { uploadImageToFiles, FILE_FOLDERS } from '../hooks/useFiles';\nimport { notify, describeError } from '../lib/toast';\n\nconst MAX_BYTES = 10 * 1024 * 1024;\nconst MAX_ATTACHMENTS = 6;\n\n// --- Composer: pick + upload images, expose file ids -----------------------\n\ninterface AttachmentPickerProps {\n fileIds: string[];\n onChange: (fileIds: string[]) => void;\n disabled?: boolean;\n}\n\nexport function AttachmentPicker({ fileIds, onChange, disabled = false }: AttachmentPickerProps) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [uploading, setUploading] = useState(false);\n const [previews, setPreviews] = useState<Record<string, string>>({});\n\n const handleSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(event.target.files ?? []);\n event.target.value = '';\n if (files.length === 0) return;\n const room = MAX_ATTACHMENTS - fileIds.length;\n if (room <= 0) {\n notify.warning('Attachment limit reached', `Up to ${MAX_ATTACHMENTS} images per post.`);\n return;\n }\n setUploading(true);\n try {\n const added: string[] = [];\n const nextPreviews: Record<string, string> = {};\n for (const file of files.slice(0, room)) {\n if (!file.type.startsWith('image/')) continue;\n if (file.size > MAX_BYTES) {\n notify.error('Image too large', `${file.name} exceeds 10 MB.`);\n continue;\n }\n const uploaded = await uploadImageToFiles(file, FILE_FOLDERS.posts);\n added.push(uploaded.id);\n nextPreviews[uploaded.id] = uploaded.url;\n }\n if (added.length) {\n setPreviews((p) => ({ ...p, ...nextPreviews }));\n onChange([...fileIds, ...added]);\n }\n } catch (err) {\n notify.error('Upload failed', describeError(err));\n } finally {\n setUploading(false);\n }\n };\n\n const remove = (id: string) => onChange(fileIds.filter((f) => f !== id));\n\n return (\n <div>\n {fileIds.length > 0 && (\n <div className=\"mb-2 flex flex-wrap gap-2\">\n {fileIds.map((id) => (\n <div\n key={id}\n className=\"relative h-20 w-20 overflow-hidden rounded-lg ring-1 ring-border-seam\"\n >\n {previews[id] ? (\n <img src={previews[id]} alt=\"attachment\" className=\"h-full w-full object-cover\" />\n ) : (\n <FileImage fileId={id} alt=\"attachment\" className=\"h-full w-full\" />\n )}\n <button\n type=\"button\"\n onClick={() => remove(id)}\n aria-label=\"Remove attachment\"\n className=\"absolute right-0.5 top-0.5 rounded-full bg-bg-canvas/70 p-0.5 text-text-primary hover:bg-bg-canvas\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n ))}\n </div>\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n className=\"sr-only\"\n onChange={handleSelect}\n disabled={disabled || uploading}\n />\n <button\n type=\"button\"\n onClick={() => inputRef.current?.click()}\n disabled={disabled || uploading || fileIds.length >= MAX_ATTACHMENTS}\n className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary transition-colors hover:text-text-primary disabled:opacity-50\"\n >\n {uploading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <ImagePlus className=\"h-4 w-4\" />\n )}\n {uploading ? 'Uploading…' : 'Add images'}\n </button>\n </div>\n );\n}\n\n// --- Reader: render a post's attachment grid -------------------------------\n\ninterface AttachmentGridProps {\n fileIds: string[];\n className?: string;\n}\n\nexport function AttachmentGrid({ fileIds, className }: AttachmentGridProps) {\n if (fileIds.length === 0) return null;\n return (\n <div\n className={cn(\n 'mt-3 grid gap-2',\n fileIds.length === 1 ? 'grid-cols-1' : 'grid-cols-2 sm:grid-cols-3',\n className\n )}\n >\n {fileIds.map((id) => (\n <div\n key={id}\n className={cn(\n 'overflow-hidden rounded-xl ring-1 ring-border-seam',\n fileIds.length === 1 ? 'max-h-96' : 'aspect-square'\n )}\n >\n <FileImage fileId={id} alt=\"Post attachment\" className=\"h-full w-full\" />\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAeA,IAAM,IAAY,KAAK,OAAO,MACxB,IAAkB;AAUxB,SAAgB,EAAiB,EAAE,YAAS,aAAU,cAAW,MAAgC;CAC/F,IAAM,IAAW,EAAgC,KAAK,EAChD,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAU,KAAe,EAAiC,EAAE,CAAC,EAE9D,IAAe,OAAO,MAA+C;EACzE,IAAM,IAAQ,MAAM,KAAK,EAAM,OAAO,SAAS,EAAE,CAAC;AAElD,MADA,EAAM,OAAO,QAAQ,IACjB,EAAM,WAAW,EAAG;EACxB,IAAM,IAAO,IAAkB,EAAQ;AACvC,MAAI,KAAQ,GAAG;AACb,KAAO,QAAQ,4BAA4B,SAAS,EAAgB,mBAAmB;AACvF;;AAEF,IAAa,GAAK;AAClB,MAAI;GACF,IAAM,IAAkB,EAAE,EACpB,IAAuC,EAAE;AAC/C,QAAK,IAAM,KAAQ,EAAM,MAAM,GAAG,EAAK,EAAE;AACvC,QAAI,CAAC,EAAK,KAAK,WAAW,SAAS,CAAE;AACrC,QAAI,EAAK,OAAO,GAAW;AACzB,OAAO,MAAM,mBAAmB,GAAG,EAAK,KAAK,iBAAiB;AAC9D;;IAEF,IAAM,IAAW,MAAM,EAAmB,GAAM,EAAa,MAAM;AAEnE,IADA,EAAM,KAAK,EAAS,GAAG,EACvB,EAAa,EAAS,MAAM,EAAS;;AAEvC,GAAI,EAAM,WACR,GAAa,OAAO;IAAE,GAAG;IAAG,GAAG;IAAc,EAAE,EAC/C,EAAS,CAAC,GAAG,GAAS,GAAG,EAAM,CAAC;WAE3B,GAAK;AACZ,KAAO,MAAM,iBAAiB,EAAc,EAAI,CAAC;YACzC;AACR,KAAa,GAAM;;IAIjB,KAAU,MAAe,EAAS,EAAQ,QAAQ,MAAM,MAAM,EAAG,CAAC;AAExE,QACE,kBAAC,OAAD,EAAA,UAAA;EACG,EAAQ,SAAS,KAChB,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAQ,KAAK,MACZ,kBAAC,OAAD;IAEE,WAAU;cAFZ,CAIG,EAAS,KACR,kBAAC,OAAD;KAAK,KAAK,EAAS;KAAK,KAAI;KAAa,WAAU;KAA+B,CAAA,GAElF,kBAAC,GAAD;KAAW,QAAQ;KAAI,KAAI;KAAa,WAAU;KAAkB,CAAA,EAEtE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAO,EAAG;KACzB,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAG,WAAU,eAAgB,CAAA;KACtB,CAAA,CACL;MAhBC,EAgBD,CACN;GACE,CAAA;EAER,kBAAC,SAAD;GACE,KAAK;GACL,MAAK;GACL,QAAO;GACP,UAAA;GACA,WAAU;GACV,UAAU;GACV,UAAU,KAAY;GACtB,CAAA;EACF,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAS,SAAS,OAAO;GACxC,UAAU,KAAY,KAAa,EAAQ,UAAU;GACrD,WAAU;aAJZ,CAMG,IACC,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAElC,IAAY,eAAe,aACrB;;EACL,EAAA,CAAA;;AAWV,SAAgB,EAAe,EAAE,YAAS,gBAAkC;AAE1E,QADI,EAAQ,WAAW,IAAU,OAE/B,kBAAC,OAAD;EACE,WAAW,EACT,mBACA,EAAQ,WAAW,IAAI,gBAAgB,8BACvC,EACD;YAEA,EAAQ,KAAK,MACZ,kBAAC,OAAD;GAEE,WAAW,EACT,sDACA,EAAQ,WAAW,IAAI,aAAa,gBACrC;aAED,kBAAC,GAAD;IAAW,QAAQ;IAAI,KAAI;IAAkB,WAAU;IAAkB,CAAA;GACrE,EAPC,EAOD,CACN;EACE,CAAA"}
@@ -0,0 +1,44 @@
1
+ import { useComradeCircle as e } from "../context/ComradeCircleContext.js";
2
+ import { cn as t } from "../utils/cn.js";
3
+ import "../context/index.js";
4
+ import { useComProfile as n } from "../hooks/useComradeCircleApi.js";
5
+ import "../hooks/index.js";
6
+ import { Avatar as r } from "./Avatar.js";
7
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
8
+ //#region src/comradecircle/components/MemberIdentity.tsx
9
+ function o(e, t) {
10
+ return e ? { name: t ? `${e} (You)` : e } : { name: t ? "You" : "Member" };
11
+ }
12
+ var s = ({ memberId: s, variant: c = "inline", size: l = "sm", suffix: u, showPresence: d = !1, onClick: f, className: p }) => {
13
+ let { currentUser: m } = e(), h = !!(m?.id && m.id === s), { data: g } = n(s), { name: _ } = o(g?.displayName ?? void 0, h), v = g?.displayName ?? (h ? m?.name ?? "You" : s), y = d ? g?.presence ?? null : null;
14
+ return /* @__PURE__ */ a(f ? "button" : "div", {
15
+ type: f ? "button" : void 0,
16
+ onClick: f,
17
+ className: t("flex items-center gap-2.5 text-left", f && "rounded-lg transition-colors hover:opacity-80", p),
18
+ children: [/* @__PURE__ */ i(r, {
19
+ name: v,
20
+ size: l,
21
+ presence: y,
22
+ fileId: g?.avatar?.id
23
+ }), /* @__PURE__ */ a("div", {
24
+ className: "min-w-0",
25
+ children: [/* @__PURE__ */ a("div", {
26
+ className: "flex items-center gap-1.5",
27
+ children: [/* @__PURE__ */ i("span", {
28
+ className: "truncate text-sm font-medium text-text-primary",
29
+ children: _
30
+ }), u && c === "inline" && /* @__PURE__ */ a("span", {
31
+ className: "shrink-0 text-xs text-text-tertiary",
32
+ children: ["· ", u]
33
+ })]
34
+ }), c === "row" && g?.headline && /* @__PURE__ */ i("p", {
35
+ className: "truncate text-xs text-text-secondary",
36
+ children: g.headline
37
+ })]
38
+ })]
39
+ });
40
+ };
41
+ //#endregion
42
+ export { s as MemberIdentity };
43
+
44
+ //# sourceMappingURL=MemberIdentity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemberIdentity.js","names":[],"sources":["../../../src/comradecircle/components/MemberIdentity.tsx"],"sourcesContent":["/**\n * MemberIdentity — resolve a `memberId` to a display name + avatar via the\n * product's own social-identity layer (`ComProfile`).\n * @module comradecircle/components\n *\n * The composed `User` entity resolves `id` only, so we never render a raw actor\n * id. Instead we look up the member's `ComProfile` (cached aggressively). The\n * calling user is labelled \"You\". If a member has not created a profile yet we\n * fall back to a neutral \"Member\" label with initials from the id.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { useComProfile } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { Avatar } from './Avatar';\nimport type { ComPresence } from '../types';\n\ninterface MemberIdentityProps {\n memberId: string;\n /** Layout: 'inline' = avatar + name only; 'row' = avatar + name + headline. */\n variant?: 'inline' | 'row';\n size?: 'xs' | 'sm' | 'md' | 'lg';\n /** Suffix shown after the name (e.g. a timestamp) in inline variant. */\n suffix?: string;\n showPresence?: boolean;\n onClick?: () => void;\n className?: string;\n}\n\nfunction labelForMember(\n displayName: string | undefined,\n isSelf: boolean\n): { name: string; presence?: ComPresence | null } {\n if (displayName) return { name: isSelf ? `${displayName} (You)` : displayName };\n return { name: isSelf ? 'You' : 'Member' };\n}\n\nexport const MemberIdentity: FC<MemberIdentityProps> = ({\n memberId,\n variant = 'inline',\n size = 'sm',\n suffix,\n showPresence = false,\n onClick,\n className,\n}) => {\n const { currentUser } = useComradeCircle();\n const isSelf = Boolean(currentUser?.id && currentUser.id === memberId);\n const { data: profile } = useComProfile(memberId);\n\n const { name } = labelForMember(profile?.displayName ?? undefined, isSelf);\n const nameForAvatar = profile?.displayName ?? (isSelf ? (currentUser?.name ?? 'You') : memberId);\n const presence = showPresence ? (profile?.presence ?? null) : null;\n\n const Wrapper = onClick ? 'button' : 'div';\n\n return (\n <Wrapper\n type={onClick ? 'button' : undefined}\n onClick={onClick}\n className={cn(\n 'flex items-center gap-2.5 text-left',\n onClick && 'rounded-lg transition-colors hover:opacity-80',\n className\n )}\n >\n <Avatar name={nameForAvatar} size={size} presence={presence} fileId={profile?.avatar?.id} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{name}</span>\n {suffix && variant === 'inline' && (\n <span className=\"shrink-0 text-xs text-text-tertiary\">· {suffix}</span>\n )}\n </div>\n {variant === 'row' && profile?.headline && (\n <p className=\"truncate text-xs text-text-secondary\">{profile.headline}</p>\n )}\n </div>\n </Wrapper>\n );\n};\n"],"mappings":";;;;;;;;AA8BA,SAAS,EACP,GACA,GACiD;AAEjD,QADI,IAAoB,EAAE,MAAM,IAAS,GAAG,EAAY,UAAU,GAAa,GACxE,EAAE,MAAM,IAAS,QAAQ,UAAU;;AAG5C,IAAa,KAA2C,EACtD,aACA,aAAU,UACV,UAAO,MACP,WACA,kBAAe,IACf,YACA,mBACI;CACJ,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAS,GAAQ,GAAa,MAAM,EAAY,OAAO,IACvD,EAAE,MAAM,MAAY,EAAc,EAAS,EAE3C,EAAE,YAAS,EAAe,GAAS,eAAe,KAAA,GAAW,EAAO,EACpE,IAAgB,GAAS,gBAAgB,IAAU,GAAa,QAAQ,QAAS,IACjF,IAAW,IAAgB,GAAS,YAAY,OAAQ;AAI9D,QACE,kBAHc,IAAU,WAAW,OAGnC;EACE,MAAM,IAAU,WAAW,KAAA;EAClB;EACT,WAAW,EACT,uCACA,KAAW,iDACX,EACD;YAPH,CASE,kBAAC,GAAD;GAAQ,MAAM;GAAqB;GAAgB;GAAU,QAAQ,GAAS,QAAQ;GAAM,CAAA,EAC5F,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAkD;KAAY,CAAA,EAC7E,KAAU,MAAY,YACrB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAsD,MAAG,EAAc;OAErE;OACL,MAAY,SAAS,GAAS,YAC7B,kBAAC,KAAD;IAAG,WAAU;cAAwC,EAAQ;IAAa,CAAA,CAExE;KACE"}
@@ -0,0 +1,22 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { jsx as t } from "react/jsx-runtime";
3
+ //#region src/comradecircle/components/MiniBar.tsx
4
+ function n({ value: n, className: r, barClassName: i = "bg-accent", height: a = 6 }) {
5
+ let o = Math.max(0, Math.min(100, n));
6
+ return /* @__PURE__ */ t("div", {
7
+ className: e("w-full overflow-hidden rounded-full bg-bg-sunken", r),
8
+ style: { height: a },
9
+ role: "progressbar",
10
+ "aria-valuenow": Math.round(o),
11
+ "aria-valuemin": 0,
12
+ "aria-valuemax": 100,
13
+ children: /* @__PURE__ */ t("div", {
14
+ className: e("h-full rounded-full transition-[width] duration-700 ease-out", i),
15
+ style: { width: `${o}%` }
16
+ })
17
+ });
18
+ }
19
+ //#endregion
20
+ export { n as MiniBar };
21
+
22
+ //# sourceMappingURL=MiniBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MiniBar.js","names":[],"sources":["../../../src/comradecircle/components/MiniBar.tsx"],"sourcesContent":["/**\n * MiniBar — a thin inline progress bar (target progress, completion, breach %).\n * @module comradecircle/components\n */\n\nimport { cn } from '../utils/cn';\n\ninterface MiniBarProps {\n /** 0–100. */\n value: number;\n className?: string;\n /** Bar fill token class (defaults to accent). */\n barClassName?: string;\n /** Height in px. */\n height?: number;\n}\n\nexport function MiniBar({\n value,\n className,\n barClassName = 'bg-accent',\n height = 6,\n}: MiniBarProps) {\n const clamped = Math.max(0, Math.min(100, value));\n return (\n <div\n className={cn('w-full overflow-hidden rounded-full bg-bg-sunken', className)}\n style={{ height }}\n role=\"progressbar\"\n aria-valuenow={Math.round(clamped)}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n <div\n className={cn('h-full rounded-full transition-[width] duration-700 ease-out', barClassName)}\n style={{ width: `${clamped}%` }}\n />\n </div>\n );\n}\n"],"mappings":";;;AAiBA,SAAgB,EAAQ,EACtB,UACA,cACA,kBAAe,aACf,YAAS,KACM;CACf,IAAM,IAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,EAAM,CAAC;AACjD,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,oDAAoD,EAAU;EAC5E,OAAO,EAAE,WAAQ;EACjB,MAAK;EACL,iBAAe,KAAK,MAAM,EAAQ;EAClC,iBAAe;EACf,iBAAe;YAEf,kBAAC,OAAD;GACE,WAAW,EAAG,gEAAgE,EAAa;GAC3F,OAAO,EAAE,OAAO,GAAG,EAAQ,IAAI;GAC/B,CAAA;EACE,CAAA"}
@@ -0,0 +1,38 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ import { AuroraBackground as r, GradientText as i } from "@burdenoff/fe-libs/ui";
4
+ //#region src/comradecircle/components/PageLayout.tsx
5
+ var a = ({ title: a, description: o, actions: s, children: c, className: l, titleGradient: u = !1, aurora: d = !1 }) => /* @__PURE__ */ n("div", {
6
+ className: e("flex flex-col h-full", l),
7
+ children: [/* @__PURE__ */ n("div", {
8
+ className: e("relative flex items-center justify-between gap-4 px-6 py-4 border-b border-border-seam", d ? "overflow-hidden bg-gloss-card" : "bg-bg-surface"),
9
+ children: [
10
+ d && /* @__PURE__ */ t(r, { intensity: "subtle" }),
11
+ /* @__PURE__ */ n("div", {
12
+ className: "min-w-0",
13
+ children: [u && typeof a == "string" ? /* @__PURE__ */ t(i, {
14
+ as: "h1",
15
+ className: "text-2xl font-semibold tracking-tight",
16
+ children: a
17
+ }) : /* @__PURE__ */ t("h1", {
18
+ className: "text-2xl font-semibold text-text-primary",
19
+ children: a
20
+ }), o && /* @__PURE__ */ t("p", {
21
+ className: "mt-1 text-sm text-text-secondary",
22
+ children: o
23
+ })]
24
+ }),
25
+ s && /* @__PURE__ */ t("div", {
26
+ className: "flex items-center gap-3 shrink-0",
27
+ children: s
28
+ })
29
+ ]
30
+ }), /* @__PURE__ */ t("div", {
31
+ className: "flex-1 overflow-auto p-6",
32
+ children: c
33
+ })]
34
+ });
35
+ //#endregion
36
+ export { a as PageLayout };
37
+
38
+ //# sourceMappingURL=PageLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/comradecircle/components/PageLayout.tsx"],"sourcesContent":["/**\n * Page Layout Component\n * @module comradecircle/components\n *\n * Boff UI 2.0 page wrapper. Supports a living-glass {@link AuroraBackground}\n * hero backdrop and a gradient hero title for the MFE's primary page.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AuroraBackground, GradientText } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface PageLayoutProps {\n title: ReactNode;\n description?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n /** Render the title as a gradient hero heading (reserve for the ONE primary page). */\n titleGradient?: boolean;\n /** Drop a subtle living-glass aurora behind the header (primary page only). */\n aurora?: boolean;\n}\n\nexport const PageLayout: FC<PageLayoutProps> = ({\n title,\n description,\n actions,\n children,\n className,\n titleGradient = false,\n aurora = false,\n}) => {\n return (\n <div className={cn('flex flex-col h-full', className)}>\n <div\n className={cn(\n 'relative flex items-center justify-between gap-4 px-6 py-4 border-b border-border-seam',\n aurora ? 'overflow-hidden bg-gloss-card' : 'bg-bg-surface'\n )}\n >\n {aurora && <AuroraBackground intensity=\"subtle\" />}\n <div className=\"min-w-0\">\n {titleGradient && typeof title === 'string' ? (\n <GradientText as=\"h1\" className=\"text-2xl font-semibold tracking-tight\">\n {title}\n </GradientText>\n ) : (\n <h1 className=\"text-2xl font-semibold text-text-primary\">{title}</h1>\n )}\n {description && <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>}\n </div>\n {actions && <div className=\"flex items-center gap-3 shrink-0\">{actions}</div>}\n </div>\n\n <div className=\"flex-1 overflow-auto p-6\">{children}</div>\n </div>\n );\n};\n"],"mappings":";;;;AAwBA,IAAa,KAAmC,EAC9C,UACA,gBACA,YACA,aACA,cACA,mBAAgB,IAChB,YAAS,SAGP,kBAAC,OAAD;CAAK,WAAW,EAAG,wBAAwB,EAAU;WAArD,CACE,kBAAC,OAAD;EACE,WAAW,EACT,0FACA,IAAS,kCAAkC,gBAC5C;YAJH;GAMG,KAAU,kBAAC,GAAD,EAAkB,WAAU,UAAW,CAAA;GAClD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KAAiB,OAAO,KAAU,WACjC,kBAAC,GAAD;KAAc,IAAG;KAAK,WAAU;eAC7B;KACY,CAAA,GAEf,kBAAC,MAAD;KAAI,WAAU;eAA4C;KAAW,CAAA,EAEtE,KAAe,kBAAC,KAAD;KAAG,WAAU;eAAoC;KAAgB,CAAA,CAC7E;;GACL,KAAW,kBAAC,OAAD;IAAK,WAAU;cAAoC;IAAc,CAAA;GACzE;KAEN,kBAAC,OAAD;EAAK,WAAU;EAA4B;EAAe,CAAA,CACtD"}
@@ -0,0 +1,50 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { ChevronLeft as t, ChevronRight as n } from "lucide-react";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ import { Button as a } from "@burdenoff/fe-libs/ui";
5
+ //#region src/comradecircle/components/Pagination.tsx
6
+ function o({ page: o, pageSize: s, itemCount: c, hasMore: l, onPageChange: u, className: d }) {
7
+ if (o === 0 && !l) return null;
8
+ let f = c === 0 ? 0 : o * s + 1, p = o * s + c;
9
+ return /* @__PURE__ */ i("div", {
10
+ className: e("mt-4 flex flex-col items-center justify-between gap-3 sm:flex-row", d),
11
+ children: [/* @__PURE__ */ i("p", {
12
+ className: "text-sm text-text-secondary tabular-nums",
13
+ "aria-live": "polite",
14
+ children: [
15
+ "Showing ",
16
+ f,
17
+ "–",
18
+ p
19
+ ]
20
+ }), /* @__PURE__ */ i("div", {
21
+ className: "flex items-center gap-2",
22
+ children: [
23
+ /* @__PURE__ */ i(a, {
24
+ variant: "outline",
25
+ size: "sm",
26
+ onClick: () => u(o - 1),
27
+ disabled: o === 0,
28
+ "aria-label": "Previous page",
29
+ children: [/* @__PURE__ */ r(t, { className: "mr-1 h-4 w-4" }), " Previous"]
30
+ }),
31
+ /* @__PURE__ */ i("span", {
32
+ className: "min-w-[4.5rem] text-center text-sm text-text-secondary tabular-nums",
33
+ children: ["Page ", o + 1]
34
+ }),
35
+ /* @__PURE__ */ i(a, {
36
+ variant: "outline",
37
+ size: "sm",
38
+ onClick: () => u(o + 1),
39
+ disabled: !l,
40
+ "aria-label": "Next page",
41
+ children: ["Next ", /* @__PURE__ */ r(n, { className: "ml-1 h-4 w-4" })]
42
+ })
43
+ ]
44
+ })]
45
+ });
46
+ }
47
+ //#endregion
48
+ export { o as Pagination };
49
+
50
+ //# sourceMappingURL=Pagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pagination.js","names":[],"sources":["../../../src/comradecircle/components/Pagination.tsx"],"sourcesContent":["/**\n * Pagination — cursor-aware Prev/Next control for Com* connection lists.\n * @module comradecircle/components\n *\n * The svc list connections return `pageInfo { cursor, hasMore }` and paginate by\n * cursor (offset is a no-op). Pages track a 0-indexed `page` for display and a\n * cursor stack for navigation; this control exposes Prev/Next, disabling Prev on\n * page 0 and Next when the server reports no more rows.\n */\n\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface PaginationProps {\n page: number;\n pageSize: number;\n /** Number of rows on the current page. */\n itemCount: number;\n /** Server flag: are there more rows after this page? */\n hasMore: boolean;\n onPageChange: (page: number) => void;\n className?: string;\n}\n\nexport function Pagination({\n page,\n pageSize,\n itemCount,\n hasMore,\n onPageChange,\n className,\n}: PaginationProps) {\n // Nothing to paginate: single page that also has no more rows.\n if (page === 0 && !hasMore) return null;\n\n const from = itemCount === 0 ? 0 : page * pageSize + 1;\n const to = page * pageSize + itemCount;\n\n return (\n <div\n className={cn('mt-4 flex flex-col items-center justify-between gap-3 sm:flex-row', className)}\n >\n <p className=\"text-sm text-text-secondary tabular-nums\" aria-live=\"polite\">\n Showing {from}–{to}\n </p>\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onPageChange(page - 1)}\n disabled={page === 0}\n aria-label=\"Previous page\"\n >\n <ChevronLeft className=\"mr-1 h-4 w-4\" /> Previous\n </Button>\n <span className=\"min-w-[4.5rem] text-center text-sm text-text-secondary tabular-nums\">\n Page {page + 1}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onPageChange(page + 1)}\n disabled={!hasMore}\n aria-label=\"Next page\"\n >\n Next <ChevronRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAyBA,SAAgB,EAAW,EACzB,SACA,aACA,cACA,YACA,iBACA,gBACkB;AAElB,KAAI,MAAS,KAAK,CAAC,EAAS,QAAO;CAEnC,IAAM,IAAO,MAAc,IAAI,IAAI,IAAO,IAAW,GAC/C,IAAK,IAAO,IAAW;AAE7B,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,qEAAqE,EAAU;YAD/F,CAGE,kBAAC,KAAD;GAAG,WAAU;GAA2C,aAAU;aAAlE;IAA2E;IAChE;IAAK;IAAE;IACd;MACJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,EAAa,IAAO,EAAE;KACrC,UAAU,MAAS;KACnB,cAAW;eALb,CAOE,kBAAC,GAAD,EAAa,WAAU,gBAAiB,CAAA,EAAA,YACjC;;IACT,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAsF,SAC9E,IAAO,EACR;;IACP,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,EAAa,IAAO,EAAE;KACrC,UAAU,CAAC;KACX,cAAW;eALb,CAMC,SACM,kBAAC,GAAD,EAAc,WAAU,gBAAiB,CAAA,CACvC;;IACL;KACF"}
@@ -0,0 +1,97 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { useComClosePoll as t, useComVotePoll as n } from "../hooks/useComradeCircleApi.js";
3
+ import "../hooks/index.js";
4
+ import { describeError as r, notify as i } from "../lib/toast.js";
5
+ import { formatNumber as a, formatRelativeTime as o } from "../utils/formatters.js";
6
+ import { StatusBadge as s } from "./StatusBadge.js";
7
+ import { MiniBar as c } from "./MiniBar.js";
8
+ import { CheckCircle2 as l, Lock as u } from "lucide-react";
9
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
10
+ import { Button as p, GlassCard as m } from "@burdenoff/fe-libs/ui";
11
+ //#region src/comradecircle/components/PollCard.tsx
12
+ function h({ poll: h, canManage: g = !1 }) {
13
+ let _ = n(), v = t(), y = h.options.reduce((e, t) => e + t.voteCount, 0), b = h.status === "CLOSED", x = async (e) => {
14
+ if (!b) try {
15
+ await _.mutateAsync({
16
+ pollId: h.id,
17
+ optionId: e,
18
+ circleId: h.circleId
19
+ }), i.success("Vote recorded");
20
+ } catch (e) {
21
+ i.error("Could not vote", r(e));
22
+ }
23
+ }, S = async () => {
24
+ try {
25
+ await v.mutateAsync(h.id), i.success("Poll closed");
26
+ } catch (e) {
27
+ i.error("Could not close poll", r(e));
28
+ }
29
+ };
30
+ return /* @__PURE__ */ f(m, {
31
+ treatment: "plain",
32
+ className: "p-5",
33
+ children: [
34
+ /* @__PURE__ */ f("div", {
35
+ className: "flex items-start justify-between gap-3",
36
+ children: [/* @__PURE__ */ d("h3", {
37
+ className: "text-base font-semibold text-text-primary",
38
+ children: h.question
39
+ }), /* @__PURE__ */ d(s, { status: h.status })]
40
+ }),
41
+ /* @__PURE__ */ d("div", {
42
+ className: "mt-4 flex flex-col gap-2.5",
43
+ children: [...h.options].sort((e, t) => e.position - t.position).map((t) => {
44
+ let n = y > 0 ? t.voteCount / y * 100 : 0;
45
+ return /* @__PURE__ */ f("button", {
46
+ type: "button",
47
+ onClick: () => x(t.id),
48
+ disabled: b || _.isPending,
49
+ className: e("group w-full rounded-xl border border-border-seam px-3 py-2.5 text-left transition-colors", !b && "hover:border-accent hover:bg-bg-sunken/60", b && "cursor-default"),
50
+ children: [/* @__PURE__ */ f("div", {
51
+ className: "flex items-center justify-between gap-2",
52
+ children: [/* @__PURE__ */ d("span", {
53
+ className: "text-sm font-medium text-text-primary",
54
+ children: t.label
55
+ }), /* @__PURE__ */ f("span", {
56
+ className: "shrink-0 text-xs tabular-nums text-text-secondary",
57
+ children: [
58
+ a(t.voteCount),
59
+ " · ",
60
+ Math.round(n),
61
+ "%"
62
+ ]
63
+ })]
64
+ }), /* @__PURE__ */ d(c, {
65
+ value: n,
66
+ className: "mt-2"
67
+ })]
68
+ }, t.id);
69
+ })
70
+ }),
71
+ /* @__PURE__ */ f("div", {
72
+ className: "mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3 text-xs text-text-tertiary",
73
+ children: [/* @__PURE__ */ f("span", {
74
+ className: "inline-flex items-center gap-1.5",
75
+ children: [
76
+ h.allowMultiple ? /* @__PURE__ */ d(l, { className: "h-3.5 w-3.5" }) : null,
77
+ a(y),
78
+ " vote",
79
+ y === 1 ? "" : "s",
80
+ h.isAnonymous ? " · anonymous" : "",
81
+ h.closesAt ? ` · closes ${o(h.closesAt)}` : ""
82
+ ]
83
+ }), g && !b && /* @__PURE__ */ f(p, {
84
+ size: "sm",
85
+ variant: "outline",
86
+ onClick: S,
87
+ disabled: v.isPending,
88
+ children: [/* @__PURE__ */ d(u, { className: "mr-1.5 h-3.5 w-3.5" }), " Close"]
89
+ })]
90
+ })
91
+ ]
92
+ });
93
+ }
94
+ //#endregion
95
+ export { h as PollCard };
96
+
97
+ //# sourceMappingURL=PollCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PollCard.js","names":[],"sources":["../../../src/comradecircle/components/PollCard.tsx"],"sourcesContent":["/**\n * PollCard — a circle poll with vote + live results.\n * @module comradecircle/components\n *\n * Writes `comVotePoll(pollId, optionId)` and `comClosePoll`. Options render as\n * clickable result bars; a closed poll (or one past its close time) is read-only.\n * The creator can close an open poll.\n */\n\nimport { CheckCircle2, Lock } from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MiniBar } from './MiniBar';\nimport { StatusBadge } from './StatusBadge';\nimport { useComVotePoll, useComClosePoll } from '../hooks';\nimport { formatNumber, formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPoll } from '../types';\n\ninterface PollCardProps {\n poll: ComPoll;\n /** Whether the caller may close the poll (creator / circle admin). */\n canManage?: boolean;\n}\n\nexport function PollCard({ poll, canManage = false }: PollCardProps) {\n const vote = useComVotePoll();\n const close = useComClosePoll();\n\n const totalVotes = poll.options.reduce((sum, o) => sum + o.voteCount, 0);\n const isClosed = poll.status === 'CLOSED';\n\n const doVote = async (optionId: string) => {\n if (isClosed) return;\n try {\n await vote.mutateAsync({ pollId: poll.id, optionId, circleId: poll.circleId });\n notify.success('Vote recorded');\n } catch (err) {\n notify.error('Could not vote', describeError(err));\n }\n };\n\n const doClose = async () => {\n try {\n await close.mutateAsync(poll.id);\n notify.success('Poll closed');\n } catch (err) {\n notify.error('Could not close poll', describeError(err));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <h3 className=\"text-base font-semibold text-text-primary\">{poll.question}</h3>\n <StatusBadge status={poll.status} />\n </div>\n\n <div className=\"mt-4 flex flex-col gap-2.5\">\n {[...poll.options]\n .sort((a, b) => a.position - b.position)\n .map((opt) => {\n const pct = totalVotes > 0 ? (opt.voteCount / totalVotes) * 100 : 0;\n return (\n <button\n key={opt.id}\n type=\"button\"\n onClick={() => doVote(opt.id)}\n disabled={isClosed || vote.isPending}\n className={cn(\n 'group w-full rounded-xl border border-border-seam px-3 py-2.5 text-left transition-colors',\n !isClosed && 'hover:border-accent hover:bg-bg-sunken/60',\n isClosed && 'cursor-default'\n )}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{opt.label}</span>\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {formatNumber(opt.voteCount)} · {Math.round(pct)}%\n </span>\n </div>\n <MiniBar value={pct} className=\"mt-2\" />\n </button>\n );\n })}\n </div>\n\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3 text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n {poll.allowMultiple ? <CheckCircle2 className=\"h-3.5 w-3.5\" /> : null}\n {formatNumber(totalVotes)} vote{totalVotes === 1 ? '' : 's'}\n {poll.isAnonymous ? ' · anonymous' : ''}\n {poll.closesAt ? ` · closes ${formatRelativeTime(poll.closesAt)}` : ''}\n </span>\n {canManage && !isClosed && (\n <Button size=\"sm\" variant=\"outline\" onClick={doClose} disabled={close.isPending}>\n <Lock className=\"mr-1.5 h-3.5 w-3.5\" /> Close\n </Button>\n )}\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;;AAyBA,SAAgB,EAAS,EAAE,SAAM,eAAY,MAAwB;CACnE,IAAM,IAAO,GAAgB,EACvB,IAAQ,GAAiB,EAEzB,IAAa,EAAK,QAAQ,QAAQ,GAAK,MAAM,IAAM,EAAE,WAAW,EAAE,EAClE,IAAW,EAAK,WAAW,UAE3B,IAAS,OAAO,MAAqB;AACrC,SACJ,KAAI;AAEF,GADA,MAAM,EAAK,YAAY;IAAE,QAAQ,EAAK;IAAI;IAAU,UAAU,EAAK;IAAU,CAAC,EAC9E,EAAO,QAAQ,gBAAgB;WACxB,GAAK;AACZ,KAAO,MAAM,kBAAkB,EAAc,EAAI,CAAC;;IAIhD,IAAU,YAAY;AAC1B,MAAI;AAEF,GADA,MAAM,EAAM,YAAY,EAAK,GAAG,EAChC,EAAO,QAAQ,cAAc;WACtB,GAAK;AACZ,KAAO,MAAM,wBAAwB,EAAc,EAAI,CAAC;;;AAI5D,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA6C,EAAK;KAAc,CAAA,EAC9E,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA,CAChC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,CAAC,GAAG,EAAK,QAAQ,CACf,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,CACvC,KAAK,MAAQ;KACZ,IAAM,IAAM,IAAa,IAAK,EAAI,YAAY,IAAc,MAAM;AAClE,YACE,kBAAC,UAAD;MAEE,MAAK;MACL,eAAe,EAAO,EAAI,GAAG;MAC7B,UAAU,KAAY,EAAK;MAC3B,WAAW,EACT,6FACA,CAAC,KAAY,6CACb,KAAY,iBACb;gBATH,CAWE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAyC,EAAI;QAAa,CAAA,EAC1E,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAa,EAAI,UAAU;SAAC;SAAI,KAAK,MAAM,EAAI;SAAC;SAC5C;UACH;UACN,kBAAC,GAAD;OAAS,OAAO;OAAK,WAAU;OAAS,CAAA,CACjC;QAjBF,EAAI,GAiBF;MAEX;IACA,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,EAAK,gBAAgB,kBAAC,GAAD,EAAc,WAAU,eAAgB,CAAA,GAAG;MAChE,EAAa,EAAW;MAAC;MAAM,MAAe,IAAI,KAAK;MACvD,EAAK,cAAc,iBAAiB;MACpC,EAAK,WAAW,aAAa,EAAmB,EAAK,SAAS,KAAK;MAC/D;QACN,KAAa,CAAC,KACb,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,SAAS;KAAS,UAAU,EAAM;eAAtE,CACE,kBAAC,GAAD,EAAM,WAAU,sBAAuB,CAAA,EAAA,SAChC;OAEP;;GACI"}