@burdenoff/microfe-comradecircle 2026.704.1 → 2026.704.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/comradecircle/components/Avatar.js +52 -17
  2. package/dist/comradecircle/components/Avatar.js.map +1 -1
  3. package/dist/comradecircle/components/AvatarUploadField.js +91 -0
  4. package/dist/comradecircle/components/AvatarUploadField.js.map +1 -0
  5. package/dist/comradecircle/components/CircleCard.js +55 -52
  6. package/dist/comradecircle/components/CircleCard.js.map +1 -1
  7. package/dist/comradecircle/components/CommentThread.js +80 -64
  8. package/dist/comradecircle/components/CommentThread.js.map +1 -1
  9. package/dist/comradecircle/components/CreateCircleDialog.js +74 -63
  10. package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -1
  11. package/dist/comradecircle/components/EventCard.js +84 -58
  12. package/dist/comradecircle/components/EventCard.js.map +1 -1
  13. package/dist/comradecircle/components/FileImage.js +20 -0
  14. package/dist/comradecircle/components/FileImage.js.map +1 -0
  15. package/dist/comradecircle/components/ImageAttachments.js +97 -0
  16. package/dist/comradecircle/components/ImageAttachments.js.map +1 -0
  17. package/dist/comradecircle/components/MemberIdentity.js +4 -2
  18. package/dist/comradecircle/components/MemberIdentity.js.map +1 -1
  19. package/dist/comradecircle/components/PollCard.js +65 -44
  20. package/dist/comradecircle/components/PollCard.js.map +1 -1
  21. package/dist/comradecircle/components/PostCard.js +8 -15
  22. package/dist/comradecircle/components/PostCard.js.map +1 -1
  23. package/dist/comradecircle/components/PostComposer.js +45 -35
  24. package/dist/comradecircle/components/PostComposer.js.map +1 -1
  25. package/dist/comradecircle/components/ReactionBar.js +1 -1
  26. package/dist/comradecircle/components/ReactionBar.js.map +1 -1
  27. package/dist/comradecircle/components/StateViews.js +1 -16
  28. package/dist/comradecircle/components/StateViews.js.map +1 -1
  29. package/dist/comradecircle/components/StatusBadge.js +1 -1
  30. package/dist/comradecircle/components/StatusBadge.js.map +1 -1
  31. package/dist/comradecircle/hooks/index.js +1 -0
  32. package/dist/comradecircle/hooks/useComradeCircleApi.js +244 -241
  33. package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
  34. package/dist/comradecircle/hooks/useFiles.js +78 -0
  35. package/dist/comradecircle/hooks/useFiles.js.map +1 -0
  36. package/dist/comradecircle/lib/graphql-cache.js +9 -0
  37. package/dist/comradecircle/lib/graphql-cache.js.map +1 -0
  38. package/dist/comradecircle/pages/FeedPage.js +79 -33
  39. package/dist/comradecircle/pages/FeedPage.js.map +1 -1
  40. package/dist/comradecircle/pages/circles/CircleDetailPage.js +354 -242
  41. package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
  42. package/dist/comradecircle/pages/circles/CirclesPage.js +97 -47
  43. package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
  44. package/dist/comradecircle/pages/events/EventDetailPage.js +193 -126
  45. package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
  46. package/dist/comradecircle/pages/events/EventsPage.js +74 -34
  47. package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
  48. package/dist/comradecircle/pages/members/MemberProfilePage.js +154 -81
  49. package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
  50. package/dist/comradecircle/pages/members/MembersPage.js +141 -71
  51. package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
  52. package/dist/comradecircle/pages/profile/MyProfilePage.js +298 -191
  53. package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
  54. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThread.js","names":[],"sources":["../../../src/comradecircle/components/CommentThread.tsx"],"sourcesContent":["/**\n * CommentThread — threaded comments for a post.\n * @module comradecircle/components\n *\n * Reads `comListComments(postId)`; writes `comAddComment`. Top-level comments\n * (parentId == null) render replies one level deep. Author identity comes from\n * `ComProfile` via {@link MemberIdentity}.\n */\n\nimport { useMemo, useState } from 'react';\nimport { CornerDownRight, Send } from 'lucide-react';\nimport { Button, Textarea, Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComComments, useComAddComment } from '../hooks';\nimport { MemberIdentity } from './MemberIdentity';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComComment } from '../types';\n\ninterface CommentThreadProps {\n postId: string;\n}\n\nexport function CommentThread({ postId }: CommentThreadProps) {\n const { data: comments, isLoading } = useComComments(postId);\n const addComment = useComAddComment();\n const [body, setBody] = useState('');\n const [replyTo, setReplyTo] = useState<ComComment | null>(null);\n\n const { roots, repliesByParent } = useMemo(() => {\n const list = comments ?? [];\n const repliesMap = new Map<string, ComComment[]>();\n const top: ComComment[] = [];\n for (const c of list) {\n if (c.parentId) {\n const arr = repliesMap.get(c.parentId) ?? [];\n arr.push(c);\n repliesMap.set(c.parentId, arr);\n } else {\n top.push(c);\n }\n }\n return { roots: top, repliesByParent: repliesMap };\n }, [comments]);\n\n const submit = async () => {\n const text = body.trim();\n if (!text) return;\n try {\n await addComment.mutateAsync({ postId, body: text, parentId: replyTo?.id ?? null });\n setBody('');\n setReplyTo(null);\n } catch (err) {\n notify.error('Could not add comment', describeError(err));\n }\n };\n\n const renderComment = (c: ComComment, isReply = false) => (\n <div key={c.id} className={cn('flex flex-col gap-1', isReply && 'ml-8')}>\n <div className=\"rounded-xl bg-bg-sunken/60 px-3 py-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <MemberIdentity\n memberId={c.authorId}\n size=\"xs\"\n suffix={formatRelativeTime(c.createdAt)}\n />\n {!isReply && (\n <button\n type=\"button\"\n onClick={() => setReplyTo(c)}\n className=\"inline-flex items-center gap-1 text-xs text-text-tertiary transition-colors hover:text-text-primary\"\n >\n <CornerDownRight className=\"h-3 w-3\" /> Reply\n </button>\n )}\n </div>\n <p className=\"mt-1 whitespace-pre-wrap break-words text-sm text-text-primary\">{c.body}</p>\n </div>\n {(repliesByParent.get(c.id) ?? []).map((r) => renderComment(r, true))}\n </div>\n );\n\n return (\n <div className=\"mt-3 border-t border-border-seam pt-3\">\n {isLoading ? (\n <div className=\"flex justify-center py-4\">\n <Spinner size=\"sm\" />\n </div>\n ) : roots.length === 0 ? (\n <p className=\"py-2 text-center text-sm text-text-tertiary\">\n No comments yet. Start the conversation.\n </p>\n ) : (\n <div className=\"flex flex-col gap-3\">{roots.map((c) => renderComment(c))}</div>\n )}\n\n <div className=\"mt-3 flex flex-col gap-2\">\n {replyTo && (\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <span className=\"truncate\">Replying to a comment</span>\n <button\n type=\"button\"\n onClick={() => setReplyTo(null)}\n className=\"text-text-tertiary hover:text-text-primary\"\n >\n Cancel\n </button>\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Add a comment…\"\n rows={1}\n className=\"min-h-[40px] resize-none\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void submit();\n }\n }}\n />\n <Button\n size=\"sm\"\n onClick={submit}\n disabled={!body.trim() || addComment.isPending}\n aria-label=\"Post comment\"\n >\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAuBA,SAAgB,EAAc,EAAE,aAA8B;CAC5D,IAAM,EAAE,MAAM,GAAU,iBAAc,EAAe,EAAO,EACtD,IAAa,GAAkB,EAC/B,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAS,KAAc,EAA4B,KAAK,EAEzD,EAAE,UAAO,uBAAoB,QAAc;EAC/C,IAAM,IAAO,KAAY,EAAE,EACrB,oBAAa,IAAI,KAA2B,EAC5C,IAAoB,EAAE;AAC5B,OAAK,IAAM,KAAK,EACd,KAAI,EAAE,UAAU;GACd,IAAM,IAAM,EAAW,IAAI,EAAE,SAAS,IAAI,EAAE;AAE5C,GADA,EAAI,KAAK,EAAE,EACX,EAAW,IAAI,EAAE,UAAU,EAAI;QAE/B,GAAI,KAAK,EAAE;AAGf,SAAO;GAAE,OAAO;GAAK,iBAAiB;GAAY;IACjD,CAAC,EAAS,CAAC,EAER,IAAS,YAAY;EACzB,IAAM,IAAO,EAAK,MAAM;AACnB,QACL,KAAI;AAGF,GAFA,MAAM,EAAW,YAAY;IAAE;IAAQ,MAAM;IAAM,UAAU,GAAS,MAAM;IAAM,CAAC,EACnF,EAAQ,GAAG,EACX,EAAW,KAAK;WACT,GAAK;AACZ,KAAO,MAAM,yBAAyB,EAAc,EAAI,CAAC;;IAIvD,KAAiB,GAAe,IAAU,OAC9C,kBAAC,OAAD;EAAgB,WAAW,EAAG,uBAAuB,KAAW,OAAO;YAAvE,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,UAAU,EAAE;KACZ,MAAK;KACL,QAAQ,EAAmB,EAAE,UAAU;KACvC,CAAA,EACD,CAAC,KACA,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,EAAE;KAC5B,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAiB,WAAU,WAAY,CAAA,EAAA,SAChC;OAEP;OACN,kBAAC,KAAD;IAAG,WAAU;cAAkE,EAAE;IAAS,CAAA,CACtF;OACJ,EAAgB,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,MAAM,EAAc,GAAG,GAAK,CAAC,CACjE;IArBI,EAAE,GAqBN;AAGR,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,IACC,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA;GACjB,CAAA,GACJ,EAAM,WAAW,IACnB,kBAAC,KAAD;GAAG,WAAU;aAA8C;GAEvD,CAAA,GAEJ,kBAAC,OAAD;GAAK,WAAU;aAAuB,EAAM,KAAK,MAAM,EAAc,EAAE,CAAC;GAAO,CAAA,EAGjF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAW;KAA4B,CAAA,EACvD,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,KAAK;KAC/B,WAAU;eACX;KAEQ,CAAA,CACL;OAER,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;KACxC,aAAY;KACZ,MAAM;KACN,WAAU;KACV,YAAY,MAAM;AAChB,MAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,aACvC,EAAE,gBAAgB,EACb,GAAQ;;KAGjB,CAAA,EACF,kBAAC,GAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU,CAAC,EAAK,MAAM,IAAI,EAAW;KACrC,cAAW;eAEX,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;KACrB,CAAA,CACL;MACF;KACF"}
1
+ {"version":3,"file":"CommentThread.js","names":[],"sources":["../../../src/comradecircle/components/CommentThread.tsx"],"sourcesContent":["/**\n * CommentThread — threaded comments for a post.\n * @module comradecircle/components\n *\n * Reads `comListComments(postId)`; writes `comAddComment`. Top-level comments\n * (parentId == null) render replies one level deep. Author identity comes from\n * `ComProfile` via {@link MemberIdentity}.\n */\n\nimport { useMemo, useState } from 'react';\nimport { CornerDownRight, Send, MessageCircle } from 'lucide-react';\nimport { Button, Textarea, Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComComments, useComAddComment } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { MemberIdentity } from './MemberIdentity';\nimport { Avatar } from './Avatar';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComComment } from '../types';\n\ninterface CommentThreadProps {\n postId: string;\n}\n\nexport function CommentThread({ postId }: CommentThreadProps) {\n const { currentUser } = useComradeCircle();\n const { data: comments, isLoading } = useComComments(postId);\n const addComment = useComAddComment();\n const [body, setBody] = useState('');\n const [replyTo, setReplyTo] = useState<ComComment | null>(null);\n\n const { roots, repliesByParent } = useMemo(() => {\n const list = comments ?? [];\n const repliesMap = new Map<string, ComComment[]>();\n const top: ComComment[] = [];\n for (const c of list) {\n if (c.parentId) {\n const arr = repliesMap.get(c.parentId) ?? [];\n arr.push(c);\n repliesMap.set(c.parentId, arr);\n } else {\n top.push(c);\n }\n }\n return { roots: top, repliesByParent: repliesMap };\n }, [comments]);\n\n const submit = async () => {\n const text = body.trim();\n if (!text) return;\n try {\n await addComment.mutateAsync({ postId, body: text, parentId: replyTo?.id ?? null });\n setBody('');\n setReplyTo(null);\n } catch (err) {\n notify.error('Could not add comment', describeError(err));\n }\n };\n\n const renderComment = (c: ComComment, isReply = false) => (\n <div key={c.id} className={cn('flex flex-col gap-1', isReply && 'ml-8')}>\n <div className=\"rounded-xl bg-bg-sunken/60 px-3 py-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <MemberIdentity\n memberId={c.authorId}\n size=\"xs\"\n suffix={formatRelativeTime(c.createdAt)}\n />\n {!isReply && (\n <button\n type=\"button\"\n onClick={() => setReplyTo(c)}\n className=\"inline-flex items-center gap-1 text-xs text-text-tertiary transition-colors hover:text-text-primary\"\n >\n <CornerDownRight className=\"h-3 w-3\" /> Reply\n </button>\n )}\n </div>\n <p className=\"mt-1 whitespace-pre-wrap break-words text-sm text-text-primary\">{c.body}</p>\n </div>\n {(repliesByParent.get(c.id) ?? []).map((r) => renderComment(r, true))}\n </div>\n );\n\n return (\n <div className=\"mt-3 border-t border-border-seam pt-3\">\n {isLoading ? (\n <div className=\"flex justify-center py-4\">\n <Spinner size=\"sm\" />\n </div>\n ) : roots.length === 0 ? (\n <div className=\"flex flex-col items-center gap-1.5 py-4 text-center\">\n <MessageCircle className=\"h-6 w-6 text-text-tertiary opacity-60\" />\n <p className=\"text-sm text-text-tertiary\">No comments yet — start the conversation.</p>\n </div>\n ) : (\n <div className=\"flex flex-col gap-3\">{roots.map((c) => renderComment(c))}</div>\n )}\n\n <div className=\"mt-3 flex flex-col gap-2\">\n {replyTo && (\n <div className=\"flex items-center justify-between rounded-lg bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <span className=\"truncate\">Replying to a comment</span>\n <button\n type=\"button\"\n onClick={() => setReplyTo(null)}\n className=\"text-text-tertiary hover:text-text-primary\"\n >\n Cancel\n </button>\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <Avatar\n name={currentUser?.name ?? currentUser?.email ?? 'You'}\n seed={currentUser?.id}\n size=\"sm\"\n className=\"mb-1\"\n />\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Add a comment…\"\n rows={1}\n className=\"min-h-[40px] resize-none\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void submit();\n }\n }}\n />\n <Button\n size=\"icon\"\n onClick={submit}\n disabled={!body.trim() || addComment.isPending}\n aria-label=\"Post comment\"\n className=\"shrink-0 rounded-full\"\n >\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -4,138 +4,149 @@ import { describeError as t, notify as n } from "../lib/toast.js";
4
4
  import { formatEnumLabel as r } from "../utils/formatters.js";
5
5
  import { slugify as i } from "../utils/slug.js";
6
6
  import { TagInput as a } from "./TagInput.js";
7
- import { useState as o } from "react";
8
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
9
- import { Button as l, Input as u, Label as d, ResponsiveDialog as f, ResponsiveDialogContent as p, ResponsiveDialogDescription as m, ResponsiveDialogFooter as h, ResponsiveDialogHeader as g, ResponsiveDialogTitle as _, Select as v, SelectContent as y, SelectItem as b, SelectTrigger as x, SelectValue as S, Textarea as C } from "@burdenoff/fe-libs/ui";
7
+ import { AvatarUploadField as o } from "./AvatarUploadField.js";
8
+ import { useState as s } from "react";
9
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
10
+ import { Button as u, Input as d, Label as f, ResponsiveDialog as p, ResponsiveDialogContent as m, ResponsiveDialogDescription as h, ResponsiveDialogFooter as g, ResponsiveDialogHeader as _, ResponsiveDialogTitle as v, Select as y, SelectContent as b, SelectItem as x, SelectTrigger as S, SelectValue as C, Textarea as w } from "@burdenoff/fe-libs/ui";
10
11
  //#region src/comradecircle/components/CreateCircleDialog.tsx
11
- var w = [
12
+ var T = [
12
13
  "PUBLIC",
13
14
  "PRIVATE",
14
15
  "SECRET"
15
16
  ];
16
- function T({ open: T, onOpenChange: E, onCreated: D }) {
17
- let O = e(), [k, A] = o(""), [j, M] = o(""), [N, P] = o(!1), [F, I] = o(""), [L, R] = o(""), [z, B] = o("PUBLIC"), [V, H] = o([]), [U, W] = o(null), G = N ? j : i(k), K = () => {
18
- A(""), M(""), P(!1), I(""), R(""), B("PUBLIC"), H([]), W(null);
17
+ function E({ open: E, onOpenChange: D, onCreated: O }) {
18
+ let k = e(), [A, j] = s(""), [M, N] = s(""), [P, F] = s(!1), [I, L] = s(""), [R, z] = s(""), [B, V] = s("PUBLIC"), [H, U] = s([]), [W, G] = s(null), [K, q] = s(null), J = P ? M : i(A), Y = () => {
19
+ j(""), N(""), F(!1), L(""), z(""), V("PUBLIC"), U([]), G(null), q(null);
19
20
  };
20
- return /* @__PURE__ */ s(f, {
21
- open: T,
22
- onOpenChange: E,
23
- children: /* @__PURE__ */ c(p, {
21
+ return /* @__PURE__ */ c(p, {
22
+ open: E,
23
+ onOpenChange: D,
24
+ children: /* @__PURE__ */ l(m, {
24
25
  className: "sm:max-w-lg",
25
26
  children: [
26
- /* @__PURE__ */ c(g, { children: [/* @__PURE__ */ s(_, { children: "Create a circle" }), /* @__PURE__ */ s(m, { children: "A circle is a community space with its own channels, members, events, and polls." })] }),
27
- /* @__PURE__ */ c("div", {
27
+ /* @__PURE__ */ l(_, { children: [/* @__PURE__ */ c(v, { children: "Create a circle" }), /* @__PURE__ */ c(h, { children: "A circle is a community space with its own channels, members, events, and polls." })] }),
28
+ /* @__PURE__ */ l("div", {
28
29
  className: "flex flex-col gap-4 py-2",
29
30
  children: [
30
- /* @__PURE__ */ c("div", {
31
+ /* @__PURE__ */ l("div", {
31
32
  className: "grid gap-1.5",
32
- children: [/* @__PURE__ */ s(d, {
33
+ children: [/* @__PURE__ */ c(f, { children: "Avatar" }), /* @__PURE__ */ c(o, {
34
+ fileId: W,
35
+ name: A || "Circle",
36
+ onUploaded: G,
37
+ label: "Upload circle avatar"
38
+ })]
39
+ }),
40
+ /* @__PURE__ */ l("div", {
41
+ className: "grid gap-1.5",
42
+ children: [/* @__PURE__ */ c(f, {
33
43
  htmlFor: "circle-name",
34
44
  children: "Name"
35
- }), /* @__PURE__ */ s(u, {
45
+ }), /* @__PURE__ */ c(d, {
36
46
  id: "circle-name",
37
- value: k,
38
- onChange: (e) => A(e.target.value),
47
+ value: A,
48
+ onChange: (e) => j(e.target.value),
39
49
  placeholder: "e.g. Product Guild"
40
50
  })]
41
51
  }),
42
- /* @__PURE__ */ c("div", {
52
+ /* @__PURE__ */ l("div", {
43
53
  className: "grid gap-1.5",
44
- children: [/* @__PURE__ */ s(d, {
54
+ children: [/* @__PURE__ */ c(f, {
45
55
  htmlFor: "circle-slug",
46
56
  children: "URL slug"
47
- }), /* @__PURE__ */ s(u, {
57
+ }), /* @__PURE__ */ c(d, {
48
58
  id: "circle-slug",
49
- value: G,
59
+ value: J,
50
60
  onChange: (e) => {
51
- P(!0), M(i(e.target.value));
61
+ F(!0), N(i(e.target.value));
52
62
  },
53
63
  placeholder: "product-guild"
54
64
  })]
55
65
  }),
56
- /* @__PURE__ */ c("div", {
66
+ /* @__PURE__ */ l("div", {
57
67
  className: "grid gap-1.5",
58
- children: [/* @__PURE__ */ s(d, {
68
+ children: [/* @__PURE__ */ c(f, {
59
69
  htmlFor: "circle-topic",
60
70
  children: "Topic"
61
- }), /* @__PURE__ */ s(u, {
71
+ }), /* @__PURE__ */ c(d, {
62
72
  id: "circle-topic",
63
- value: F,
64
- onChange: (e) => I(e.target.value),
73
+ value: I,
74
+ onChange: (e) => L(e.target.value),
65
75
  placeholder: "A short one-line topic"
66
76
  })]
67
77
  }),
68
- /* @__PURE__ */ c("div", {
78
+ /* @__PURE__ */ l("div", {
69
79
  className: "grid gap-1.5",
70
- children: [/* @__PURE__ */ s(d, {
80
+ children: [/* @__PURE__ */ c(f, {
71
81
  htmlFor: "circle-desc",
72
82
  children: "Description"
73
- }), /* @__PURE__ */ s(C, {
83
+ }), /* @__PURE__ */ c(w, {
74
84
  id: "circle-desc",
75
- value: L,
76
- onChange: (e) => R(e.target.value),
85
+ value: R,
86
+ onChange: (e) => z(e.target.value),
77
87
  placeholder: "What is this circle about?",
78
88
  rows: 3
79
89
  })]
80
90
  }),
81
- /* @__PURE__ */ c("div", {
91
+ /* @__PURE__ */ l("div", {
82
92
  className: "grid gap-1.5",
83
- children: [/* @__PURE__ */ s(d, { children: "Visibility" }), /* @__PURE__ */ c(v, {
84
- value: z,
85
- onValueChange: (e) => B(e),
86
- children: [/* @__PURE__ */ s(x, { children: /* @__PURE__ */ s(S, {}) }), /* @__PURE__ */ s(y, { children: w.map((e) => /* @__PURE__ */ s(b, {
93
+ children: [/* @__PURE__ */ c(f, { children: "Visibility" }), /* @__PURE__ */ l(y, {
94
+ value: B,
95
+ onValueChange: (e) => V(e),
96
+ children: [/* @__PURE__ */ c(S, { children: /* @__PURE__ */ c(C, {}) }), /* @__PURE__ */ c(b, { children: T.map((e) => /* @__PURE__ */ c(x, {
87
97
  value: e,
88
98
  children: r(e)
89
99
  }, e)) })]
90
100
  })]
91
101
  }),
92
- /* @__PURE__ */ c("div", {
102
+ /* @__PURE__ */ l("div", {
93
103
  className: "grid gap-1.5",
94
- children: [/* @__PURE__ */ s(d, {
104
+ children: [/* @__PURE__ */ c(f, {
95
105
  htmlFor: "circle-tags",
96
106
  children: "Tags"
97
- }), /* @__PURE__ */ s(a, {
107
+ }), /* @__PURE__ */ c(a, {
98
108
  id: "circle-tags",
99
- value: V,
100
- onChange: H,
109
+ value: H,
110
+ onChange: U,
101
111
  placeholder: "Add tags…"
102
112
  })]
103
113
  }),
104
- U && /* @__PURE__ */ s("p", {
114
+ K && /* @__PURE__ */ c("p", {
105
115
  className: "text-sm text-status-error-text",
106
- children: U
116
+ children: K
107
117
  })
108
118
  ]
109
119
  }),
110
- /* @__PURE__ */ c(h, { children: [/* @__PURE__ */ s(l, {
120
+ /* @__PURE__ */ l(g, { children: [/* @__PURE__ */ c(u, {
111
121
  variant: "outline",
112
- onClick: () => E(!1),
122
+ onClick: () => D(!1),
113
123
  children: "Cancel"
114
- }), /* @__PURE__ */ s(l, {
124
+ }), /* @__PURE__ */ c(u, {
115
125
  onClick: async () => {
116
- if (!k.trim()) {
117
- W("Circle name is required.");
126
+ if (!A.trim()) {
127
+ q("Circle name is required.");
118
128
  return;
119
129
  }
120
- if (!G) {
121
- W("A URL slug is required.");
130
+ if (!J) {
131
+ q("A URL slug is required.");
122
132
  return;
123
133
  }
124
134
  try {
125
- let e = await O.mutateAsync({
126
- name: k.trim(),
127
- slug: G,
128
- topic: F.trim() || null,
129
- description: L.trim() || null,
130
- visibility: z,
131
- tags: V.length ? V : null
135
+ let e = await k.mutateAsync({
136
+ name: A.trim(),
137
+ slug: J,
138
+ topic: I.trim() || null,
139
+ description: R.trim() || null,
140
+ visibility: B,
141
+ avatarFileId: W,
142
+ tags: H.length ? H : null
132
143
  });
133
- n.success("Circle created", `${e.name} is ready.`), E(!1), K(), D?.(e);
144
+ n.success("Circle created", `${e.name} is ready.`), D(!1), Y(), O?.(e);
134
145
  } catch (e) {
135
- W(t(e));
146
+ q(t(e));
136
147
  }
137
148
  },
138
- disabled: O.isPending,
149
+ disabled: k.isPending,
139
150
  children: "Create circle"
140
151
  })] })
141
152
  ]
@@ -143,6 +154,6 @@ function T({ open: T, onOpenChange: E, onCreated: D }) {
143
154
  });
144
155
  }
145
156
  //#endregion
146
- export { T as CreateCircleDialog };
157
+ export { E as CreateCircleDialog };
147
158
 
148
159
  //# sourceMappingURL=CreateCircleDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CreateCircleDialog.js","names":[],"sources":["../../../src/comradecircle/components/CreateCircleDialog.tsx"],"sourcesContent":["/**\n * CreateCircleDialog — create a new community circle.\n * @module comradecircle/components\n *\n * Writes `comCreateCircle`. Slug auto-derives from the name (editable).\n */\n\nimport { useState } from 'react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { TagInput } from './TagInput';\nimport { useComCreateCircle } from '../hooks';\nimport { slugify } from '../utils/slug';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComCircle, ComCircleVisibility } from '../types';\n\nconst VISIBILITIES: ComCircleVisibility[] = ['PUBLIC', 'PRIVATE', 'SECRET'];\n\ninterface CreateCircleDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCreated?: (circle: ComCircle) => void;\n}\n\nexport function CreateCircleDialog({ open, onOpenChange, onCreated }: CreateCircleDialogProps) {\n const createCircle = useComCreateCircle();\n const [name, setName] = useState('');\n const [slug, setSlug] = useState('');\n const [slugTouched, setSlugTouched] = useState(false);\n const [topic, setTopic] = useState('');\n const [description, setDescription] = useState('');\n const [visibility, setVisibility] = useState<ComCircleVisibility>('PUBLIC');\n const [tags, setTags] = useState<string[]>([]);\n const [error, setError] = useState<string | null>(null);\n\n const effectiveSlug = slugTouched ? slug : slugify(name);\n\n const reset = () => {\n setName('');\n setSlug('');\n setSlugTouched(false);\n setTopic('');\n setDescription('');\n setVisibility('PUBLIC');\n setTags([]);\n setError(null);\n };\n\n const submit = async () => {\n if (!name.trim()) {\n setError('Circle name is required.');\n return;\n }\n if (!effectiveSlug) {\n setError('A URL slug is required.');\n return;\n }\n try {\n const created = await createCircle.mutateAsync({\n name: name.trim(),\n slug: effectiveSlug,\n topic: topic.trim() || null,\n description: description.trim() || null,\n visibility,\n tags: tags.length ? tags : null,\n });\n notify.success('Circle created', `${created.name} is ready.`);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create a circle</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n A circle is a community space with its own channels, members, events, and polls.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex flex-col gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-name\">Name</Label>\n <Input\n id=\"circle-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Product Guild\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-slug\">URL slug</Label>\n <Input\n id=\"circle-slug\"\n value={effectiveSlug}\n onChange={(e) => {\n setSlugTouched(true);\n setSlug(slugify(e.target.value));\n }}\n placeholder=\"product-guild\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-topic\">Topic</Label>\n <Input\n id=\"circle-topic\"\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n placeholder=\"A short one-line topic\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-desc\">Description</Label>\n <Textarea\n id=\"circle-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"What is this circle about?\"\n rows={3}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Visibility</Label>\n <Select\n value={visibility}\n onValueChange={(v) => setVisibility(v as ComCircleVisibility)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITIES.map((v) => (\n <SelectItem key={v} value={v}>\n {formatEnumLabel(v)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-tags\">Tags</Label>\n <TagInput id=\"circle-tags\" value={tags} onChange={setTags} placeholder=\"Add tags…\" />\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={createCircle.isPending}>\n Create circle\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;AAgCA,IAAM,IAAsC;CAAC;CAAU;CAAW;CAAS;AAQ3E,SAAgB,EAAmB,EAAE,SAAM,iBAAc,gBAAsC;CAC7F,IAAM,IAAe,GAAoB,EACnC,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAY,KAAiB,EAA8B,SAAS,EACrE,CAAC,GAAM,KAAW,EAAmB,EAAE,CAAC,EACxC,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAgB,IAAc,IAAO,EAAQ,EAAK,EAElD,UAAc;AAQlB,EAPA,EAAQ,GAAG,EACX,EAAQ,GAAG,EACX,EAAe,GAAM,EACrB,EAAS,GAAG,EACZ,EAAe,GAAG,EAClB,EAAc,SAAS,EACvB,EAAQ,EAAE,CAAC,EACX,EAAS,KAAK;;AA8BhB,QACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,mBAAuC,CAAA,EAC9D,kBAAC,GAAD,EAAA,UAA6B,oFAEC,CAAA,CACP,EAAA,CAAA;IAEzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAc;QAAY,CAAA,EACzC,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;QACxC,aAAY;QACZ,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAc;QAAgB,CAAA,EAC7C,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM;AAEf,SADA,EAAe,GAAK,EACpB,EAAQ,EAAQ,EAAE,OAAO,MAAM,CAAC;;QAElC,aAAY;QACZ,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAe;QAAa,CAAA,EAC3C,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;QACzC,aAAY;QACZ,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAc;QAAmB,CAAA,EAChD,kBAAC,GAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,aAAY;QACZ,MAAM;QACN,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,cAAkB,CAAA,EACzB,kBAAC,GAAD;QACE,OAAO;QACP,gBAAgB,MAAM,EAAc,EAAyB;kBAF/D,CAIE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAe,CAAA,EACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAa,KAAK,MACjB,kBAAC,GAAD;SAAoB,OAAO;mBACxB,EAAgB,EAAE;SACR,EAFI,EAEJ,CACb,EACY,CAAA,CACT;UACL;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAQ;kBAAc;QAAY,CAAA,EACzC,kBAAC,GAAD;QAAU,IAAG;QAAc,OAAO;QAAM,UAAU;QAAS,aAAY;QAAc,CAAA,CACjF;;MACL,KAAS,kBAAC,KAAD;OAAG,WAAU;iBAAkC;OAAU,CAAA;MAC/D;;IAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAa,GAAM;eAAE;KAErD,CAAA,EACT,kBAAC,GAAD;KAAQ,SA3GD,YAAY;AACzB,UAAI,CAAC,EAAK,MAAM,EAAE;AAChB,SAAS,2BAA2B;AACpC;;AAEF,UAAI,CAAC,GAAe;AAClB,SAAS,0BAA0B;AACnC;;AAEF,UAAI;OACF,IAAM,IAAU,MAAM,EAAa,YAAY;QAC7C,MAAM,EAAK,MAAM;QACjB,MAAM;QACN,OAAO,EAAM,MAAM,IAAI;QACvB,aAAa,EAAY,MAAM,IAAI;QACnC;QACA,MAAM,EAAK,SAAS,IAAO;QAC5B,CAAC;AAIF,OAHA,EAAO,QAAQ,kBAAkB,GAAG,EAAQ,KAAK,YAAY,EAC7D,EAAa,GAAM,EACnB,GAAO,EACP,IAAY,EAAQ;eACb,GAAK;AACZ,SAAS,EAAc,EAAI,CAAC;;;KAoFC,UAAU,EAAa;eAAW;KAElD,CAAA,CACc,EAAA,CAAA;IACD;;EACT,CAAA"}
1
+ {"version":3,"file":"CreateCircleDialog.js","names":[],"sources":["../../../src/comradecircle/components/CreateCircleDialog.tsx"],"sourcesContent":["/**\n * CreateCircleDialog — create a new community circle.\n * @module comradecircle/components\n *\n * Writes `comCreateCircle`. Slug auto-derives from the name (editable).\n */\n\nimport { useState } from 'react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { TagInput } from './TagInput';\nimport { AvatarUploadField } from './AvatarUploadField';\nimport { useComCreateCircle } from '../hooks';\nimport { slugify } from '../utils/slug';\nimport { formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComCircle, ComCircleVisibility } from '../types';\n\nconst VISIBILITIES: ComCircleVisibility[] = ['PUBLIC', 'PRIVATE', 'SECRET'];\n\ninterface CreateCircleDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCreated?: (circle: ComCircle) => void;\n}\n\nexport function CreateCircleDialog({ open, onOpenChange, onCreated }: CreateCircleDialogProps) {\n const createCircle = useComCreateCircle();\n const [name, setName] = useState('');\n const [slug, setSlug] = useState('');\n const [slugTouched, setSlugTouched] = useState(false);\n const [topic, setTopic] = useState('');\n const [description, setDescription] = useState('');\n const [visibility, setVisibility] = useState<ComCircleVisibility>('PUBLIC');\n const [tags, setTags] = useState<string[]>([]);\n const [avatarFileId, setAvatarFileId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const effectiveSlug = slugTouched ? slug : slugify(name);\n\n const reset = () => {\n setName('');\n setSlug('');\n setSlugTouched(false);\n setTopic('');\n setDescription('');\n setVisibility('PUBLIC');\n setTags([]);\n setAvatarFileId(null);\n setError(null);\n };\n\n const submit = async () => {\n if (!name.trim()) {\n setError('Circle name is required.');\n return;\n }\n if (!effectiveSlug) {\n setError('A URL slug is required.');\n return;\n }\n try {\n const created = await createCircle.mutateAsync({\n name: name.trim(),\n slug: effectiveSlug,\n topic: topic.trim() || null,\n description: description.trim() || null,\n visibility,\n avatarFileId,\n tags: tags.length ? tags : null,\n });\n notify.success('Circle created', `${created.name} is ready.`);\n onOpenChange(false);\n reset();\n onCreated?.(created);\n } catch (err) {\n setError(describeError(err));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Create a circle</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n A circle is a community space with its own channels, members, events, and polls.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex flex-col gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label>Avatar</Label>\n <AvatarUploadField\n fileId={avatarFileId}\n name={name || 'Circle'}\n onUploaded={setAvatarFileId}\n label=\"Upload circle avatar\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-name\">Name</Label>\n <Input\n id=\"circle-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Product Guild\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-slug\">URL slug</Label>\n <Input\n id=\"circle-slug\"\n value={effectiveSlug}\n onChange={(e) => {\n setSlugTouched(true);\n setSlug(slugify(e.target.value));\n }}\n placeholder=\"product-guild\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-topic\">Topic</Label>\n <Input\n id=\"circle-topic\"\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n placeholder=\"A short one-line topic\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-desc\">Description</Label>\n <Textarea\n id=\"circle-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"What is this circle about?\"\n rows={3}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label>Visibility</Label>\n <Select\n value={visibility}\n onValueChange={(v) => setVisibility(v as ComCircleVisibility)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITIES.map((v) => (\n <SelectItem key={v} value={v}>\n {formatEnumLabel(v)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"circle-tags\">Tags</Label>\n <TagInput id=\"circle-tags\" value={tags} onChange={setTags} placeholder=\"Add tags…\" />\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={createCircle.isPending}>\n Create circle\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -1,93 +1,119 @@
1
- import { useComRsvpEvent as e } from "../hooks/useComradeCircleApi.js";
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { useComRsvpEvent as t } from "../hooks/useComradeCircleApi.js";
2
3
  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";
4
+ import { describeError as n, notify as r } from "../lib/toast.js";
5
+ import { formatDateTime as i, 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 { useState as l } from "react";
9
+ import { Calendar as u, Check as d, ExternalLink as f, MapPin as p, Users as m, Video as h } from "lucide-react";
10
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
11
+ import { Button as v, GlassCard as y } from "@burdenoff/fe-libs/ui";
9
12
  //#region src/comradecircle/components/EventCard.tsx
10
- var h = [{
13
+ var b = [{
11
14
  status: "GOING",
12
15
  label: "Going"
13
16
  }, {
14
17
  status: "INTERESTED",
15
18
  label: "Interested"
16
19
  }];
17
- function g({ event: g, onOpen: _ }) {
18
- let v = e(), y = g.status === "SCHEDULED" || g.status === "LIVE", b = async (e) => {
20
+ function x({ event: x, onOpen: S }) {
21
+ let C = t(), [w, T] = l(null), E = x.status === "SCHEDULED" || x.status === "LIVE", D = x.status === "LIVE", O = async (e) => {
19
22
  try {
20
- await v.mutateAsync({
21
- eventId: g.id,
23
+ await C.mutateAsync({
24
+ eventId: x.id,
22
25
  status: e
23
- }), n.success(`RSVP: ${e === "GOING" ? "Going" : "Interested"}`, g.title);
26
+ }), T(e), r.success(`RSVP: ${e === "GOING" ? "Going" : "Interested"}`, x.title);
24
27
  } catch (e) {
25
- n.error("Could not RSVP", t(e));
28
+ r.error("Could not RSVP", n(e));
26
29
  }
27
- }, x = g.mode === "IN_PERSON" ? c : u;
28
- return /* @__PURE__ */ f(m, {
30
+ }, k = x.mode === "IN_PERSON" ? p : h, A = x.capacity ? Math.min(100, x.rsvpCount / x.capacity * 100) : 0, j = A >= 80;
31
+ return /* @__PURE__ */ _(y, {
29
32
  treatment: "plain",
30
- className: "flex flex-col p-5",
33
+ className: e("flex flex-col p-5 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]", D && "ring-1 ring-[var(--color-accent-soft)]"),
31
34
  children: [
32
- /* @__PURE__ */ f("div", {
35
+ /* @__PURE__ */ _("div", {
33
36
  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 })]
37
+ children: [/* @__PURE__ */ g(s, { status: x.status }), E && /* @__PURE__ */ g("span", {
38
+ className: "text-xs font-medium text-text-tertiary",
39
+ children: o(x.startsAt)
40
+ })]
41
+ }),
42
+ /* @__PURE__ */ g("button", {
43
+ type: "button",
44
+ onClick: S,
45
+ className: "mt-2 text-left text-base font-semibold text-text-primary transition-colors hover:text-accent",
46
+ children: x.title
40
47
  }),
41
- g.description && /* @__PURE__ */ d("p", {
42
- className: "mt-2 line-clamp-2 text-sm text-text-secondary",
43
- children: g.description
48
+ x.description && /* @__PURE__ */ g("p", {
49
+ className: "mt-1.5 line-clamp-2 text-sm text-text-secondary",
50
+ children: x.description
44
51
  }),
45
- /* @__PURE__ */ f("div", {
52
+ /* @__PURE__ */ _("div", {
46
53
  className: "mt-3 flex flex-col gap-1.5 text-sm text-text-secondary",
47
- children: [/* @__PURE__ */ f("span", {
54
+ children: [/* @__PURE__ */ _("span", {
48
55
  className: "inline-flex items-center gap-2",
49
56
  children: [
50
- /* @__PURE__ */ d(o, { className: "h-4 w-4 text-text-tertiary" }),
57
+ /* @__PURE__ */ g(u, { className: "h-4 w-4 text-text-tertiary" }),
51
58
  " ",
52
- r(g.startsAt)
59
+ i(x.startsAt)
53
60
  ]
54
- }), /* @__PURE__ */ f("span", {
61
+ }), /* @__PURE__ */ _("span", {
55
62
  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"]
63
+ children: [/* @__PURE__ */ g(k, { className: "h-4 w-4 text-text-tertiary" }), x.mode === "IN_PERSON" ? x.location || "In person" : x.mode === "HYBRID" ? `Hybrid${x.location ? ` · ${x.location}` : ""}` : "Online"]
57
64
  })]
58
65
  }),
59
- /* @__PURE__ */ f("div", {
66
+ /* @__PURE__ */ _("div", {
67
+ className: "mt-3",
68
+ children: [/* @__PURE__ */ _("div", {
69
+ className: "flex items-center justify-between text-xs text-text-tertiary",
70
+ children: [/* @__PURE__ */ _("span", {
71
+ className: "inline-flex items-center gap-1",
72
+ children: [
73
+ /* @__PURE__ */ g(m, { className: "h-3.5 w-3.5" }),
74
+ " ",
75
+ a(x.rsvpCount),
76
+ x.capacity ? ` / ${a(x.capacity)}` : "",
77
+ " attending"
78
+ ]
79
+ }), x.capacity && j && /* @__PURE__ */ g("span", {
80
+ className: e(A >= 100 ? "text-status-error-text" : "text-status-warning-text"),
81
+ children: A >= 100 ? "Full" : `${Math.round(A)}% full`
82
+ })]
83
+ }), x.capacity ? /* @__PURE__ */ g(c, {
84
+ value: A,
85
+ className: "mt-1.5",
86
+ height: 4,
87
+ barClassName: A >= 100 ? "bg-status-error-text" : j ? "bg-status-warning-text" : "bg-accent"
88
+ }) : null]
89
+ }),
90
+ /* @__PURE__ */ _("div", {
60
91
  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", {
92
+ children: [D && x.joinUrl ? /* @__PURE__ */ _("a", {
93
+ href: x.joinUrl,
94
+ target: "_blank",
95
+ rel: "noreferrer noopener",
96
+ className: "inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline",
97
+ children: ["Join live ", /* @__PURE__ */ g(f, { className: "h-3 w-3" })]
98
+ }) : /* @__PURE__ */ g("span", {}), /* @__PURE__ */ g("div", {
71
99
  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))]
100
+ children: E && b.map((e) => {
101
+ let t = w === e.status;
102
+ return /* @__PURE__ */ _(v, {
103
+ size: "sm",
104
+ variant: t || e.status === "GOING" ? "default" : "outline",
105
+ onClick: () => O(e.status),
106
+ disabled: C.isPending,
107
+ "aria-pressed": t,
108
+ children: [t && /* @__PURE__ */ g(d, { className: "mr-1 h-3.5 w-3.5" }), e.label]
109
+ }, e.status);
110
+ })
85
111
  })]
86
112
  })
87
113
  ]
88
114
  });
89
115
  }
90
116
  //#endregion
91
- export { g as EventCard };
117
+ export { x as EventCard };
92
118
 
93
119
  //# sourceMappingURL=EventCard.js.map
@@ -1 +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"}
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, a relative-time cue, a\n * capacity fill bar, the caller's selected RSVP (tracked client-side after a\n * successful mutation — the list query returns no viewer RSVP), and a live-join\n * link. LIVE events get an accent ring so they pop out of the grid.\n */\n\nimport { useState } from 'react';\nimport { Calendar, MapPin, Video, Users, ExternalLink, Check } 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 { useComRsvpEvent } from '../hooks';\nimport { formatDateTime, formatNumber, formatRelativeTime } 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 [myStatus, setMyStatus] = useState<ComRsvpStatus | null>(null);\n const canRsvp = event.status === 'SCHEDULED' || event.status === 'LIVE';\n const isLive = event.status === 'LIVE';\n\n const doRsvp = async (status: ComRsvpStatus) => {\n try {\n await rsvp.mutateAsync({ eventId: event.id, status });\n setMyStatus(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 const fillPct = event.capacity ? Math.min(100, (event.rsvpCount / event.capacity) * 100) : 0;\n const nearFull = fillPct >= 80;\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'flex flex-col p-5 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]',\n isLive && 'ring-1 ring-[var(--color-accent-soft)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <StatusBadge status={event.status} />\n {canRsvp && (\n <span className=\"text-xs font-medium text-text-tertiary\">\n {formatRelativeTime(event.startsAt)}\n </span>\n )}\n </div>\n\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"mt-2 text-left text-base font-semibold text-text-primary transition-colors hover:text-accent\"\n >\n {event.title}\n </button>\n\n {event.description && (\n <p className=\"mt-1.5 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-3\">\n <div className=\"flex items-center justify-between text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" /> {formatNumber(event.rsvpCount)}\n {event.capacity ? ` / ${formatNumber(event.capacity)}` : ''} attending\n </span>\n {event.capacity && nearFull && (\n <span\n className={cn(fillPct >= 100 ? 'text-status-error-text' : 'text-status-warning-text')}\n >\n {fillPct >= 100 ? 'Full' : `${Math.round(fillPct)}% full`}\n </span>\n )}\n </div>\n {event.capacity ? (\n <MiniBar\n value={fillPct}\n className=\"mt-1.5\"\n height={4}\n barClassName={\n fillPct >= 100\n ? 'bg-status-error-text'\n : nearFull\n ? 'bg-status-warning-text'\n : 'bg-accent'\n }\n />\n ) : null}\n </div>\n\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n {isLive && 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 live <ExternalLink className=\"h-3 w-3\" />\n </a>\n ) : (\n <span />\n )}\n <div className=\"flex items-center gap-2\">\n {canRsvp &&\n RSVP_ACTIONS.map((a) => {\n const active = myStatus === a.status;\n return (\n <Button\n key={a.status}\n size=\"sm\"\n variant={active || a.status === 'GOING' ? 'default' : 'outline'}\n onClick={() => doRsvp(a.status)}\n disabled={rsvp.isPending}\n aria-pressed={active}\n >\n {active && <Check className=\"mr-1 h-3.5 w-3.5\" />}\n {a.label}\n </Button>\n );\n })}\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -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"}