@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.
- package/dist/comradecircle/components/Avatar.js +52 -17
- package/dist/comradecircle/components/Avatar.js.map +1 -1
- package/dist/comradecircle/components/AvatarUploadField.js +91 -0
- package/dist/comradecircle/components/AvatarUploadField.js.map +1 -0
- package/dist/comradecircle/components/CircleCard.js +55 -52
- package/dist/comradecircle/components/CircleCard.js.map +1 -1
- package/dist/comradecircle/components/CommentThread.js +80 -64
- package/dist/comradecircle/components/CommentThread.js.map +1 -1
- package/dist/comradecircle/components/CreateCircleDialog.js +74 -63
- package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -1
- package/dist/comradecircle/components/EventCard.js +84 -58
- package/dist/comradecircle/components/EventCard.js.map +1 -1
- package/dist/comradecircle/components/FileImage.js +20 -0
- package/dist/comradecircle/components/FileImage.js.map +1 -0
- package/dist/comradecircle/components/ImageAttachments.js +97 -0
- package/dist/comradecircle/components/ImageAttachments.js.map +1 -0
- package/dist/comradecircle/components/MemberIdentity.js +4 -2
- package/dist/comradecircle/components/MemberIdentity.js.map +1 -1
- package/dist/comradecircle/components/PollCard.js +65 -44
- package/dist/comradecircle/components/PollCard.js.map +1 -1
- package/dist/comradecircle/components/PostCard.js +8 -15
- package/dist/comradecircle/components/PostCard.js.map +1 -1
- package/dist/comradecircle/components/PostComposer.js +45 -35
- package/dist/comradecircle/components/PostComposer.js.map +1 -1
- package/dist/comradecircle/components/ReactionBar.js +1 -1
- package/dist/comradecircle/components/ReactionBar.js.map +1 -1
- package/dist/comradecircle/components/StateViews.js +1 -16
- package/dist/comradecircle/components/StateViews.js.map +1 -1
- package/dist/comradecircle/components/StatusBadge.js +1 -1
- package/dist/comradecircle/components/StatusBadge.js.map +1 -1
- package/dist/comradecircle/hooks/index.js +1 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.js +244 -241
- package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
- package/dist/comradecircle/hooks/useFiles.js +78 -0
- package/dist/comradecircle/hooks/useFiles.js.map +1 -0
- package/dist/comradecircle/lib/graphql-cache.js +9 -0
- package/dist/comradecircle/lib/graphql-cache.js.map +1 -0
- package/dist/comradecircle/pages/FeedPage.js +79 -33
- package/dist/comradecircle/pages/FeedPage.js.map +1 -1
- package/dist/comradecircle/pages/circles/CircleDetailPage.js +354 -242
- package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/circles/CirclesPage.js +97 -47
- package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
- package/dist/comradecircle/pages/events/EventDetailPage.js +193 -126
- package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
- package/dist/comradecircle/pages/events/EventsPage.js +74 -34
- package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
- package/dist/comradecircle/pages/members/MemberProfilePage.js +154 -81
- package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
- package/dist/comradecircle/pages/members/MembersPage.js +141 -71
- package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
- package/dist/comradecircle/pages/profile/MyProfilePage.js +298 -191
- package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
- 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 <
|
|
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 {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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
|
|
12
|
+
var T = [
|
|
12
13
|
"PUBLIC",
|
|
13
14
|
"PRIVATE",
|
|
14
15
|
"SECRET"
|
|
15
16
|
];
|
|
16
|
-
function
|
|
17
|
-
let
|
|
18
|
-
|
|
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__ */
|
|
21
|
-
open:
|
|
22
|
-
onOpenChange:
|
|
23
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
27
|
-
/* @__PURE__ */
|
|
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__ */
|
|
31
|
+
/* @__PURE__ */ l("div", {
|
|
31
32
|
className: "grid gap-1.5",
|
|
32
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
45
|
+
}), /* @__PURE__ */ c(d, {
|
|
36
46
|
id: "circle-name",
|
|
37
|
-
value:
|
|
38
|
-
onChange: (e) =>
|
|
47
|
+
value: A,
|
|
48
|
+
onChange: (e) => j(e.target.value),
|
|
39
49
|
placeholder: "e.g. Product Guild"
|
|
40
50
|
})]
|
|
41
51
|
}),
|
|
42
|
-
/* @__PURE__ */
|
|
52
|
+
/* @__PURE__ */ l("div", {
|
|
43
53
|
className: "grid gap-1.5",
|
|
44
|
-
children: [/* @__PURE__ */
|
|
54
|
+
children: [/* @__PURE__ */ c(f, {
|
|
45
55
|
htmlFor: "circle-slug",
|
|
46
56
|
children: "URL slug"
|
|
47
|
-
}), /* @__PURE__ */
|
|
57
|
+
}), /* @__PURE__ */ c(d, {
|
|
48
58
|
id: "circle-slug",
|
|
49
|
-
value:
|
|
59
|
+
value: J,
|
|
50
60
|
onChange: (e) => {
|
|
51
|
-
|
|
61
|
+
F(!0), N(i(e.target.value));
|
|
52
62
|
},
|
|
53
63
|
placeholder: "product-guild"
|
|
54
64
|
})]
|
|
55
65
|
}),
|
|
56
|
-
/* @__PURE__ */
|
|
66
|
+
/* @__PURE__ */ l("div", {
|
|
57
67
|
className: "grid gap-1.5",
|
|
58
|
-
children: [/* @__PURE__ */
|
|
68
|
+
children: [/* @__PURE__ */ c(f, {
|
|
59
69
|
htmlFor: "circle-topic",
|
|
60
70
|
children: "Topic"
|
|
61
|
-
}), /* @__PURE__ */
|
|
71
|
+
}), /* @__PURE__ */ c(d, {
|
|
62
72
|
id: "circle-topic",
|
|
63
|
-
value:
|
|
64
|
-
onChange: (e) =>
|
|
73
|
+
value: I,
|
|
74
|
+
onChange: (e) => L(e.target.value),
|
|
65
75
|
placeholder: "A short one-line topic"
|
|
66
76
|
})]
|
|
67
77
|
}),
|
|
68
|
-
/* @__PURE__ */
|
|
78
|
+
/* @__PURE__ */ l("div", {
|
|
69
79
|
className: "grid gap-1.5",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
80
|
+
children: [/* @__PURE__ */ c(f, {
|
|
71
81
|
htmlFor: "circle-desc",
|
|
72
82
|
children: "Description"
|
|
73
|
-
}), /* @__PURE__ */
|
|
83
|
+
}), /* @__PURE__ */ c(w, {
|
|
74
84
|
id: "circle-desc",
|
|
75
|
-
value:
|
|
76
|
-
onChange: (e) =>
|
|
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__ */
|
|
91
|
+
/* @__PURE__ */ l("div", {
|
|
82
92
|
className: "grid gap-1.5",
|
|
83
|
-
children: [/* @__PURE__ */
|
|
84
|
-
value:
|
|
85
|
-
onValueChange: (e) =>
|
|
86
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
102
|
+
/* @__PURE__ */ l("div", {
|
|
93
103
|
className: "grid gap-1.5",
|
|
94
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ c(f, {
|
|
95
105
|
htmlFor: "circle-tags",
|
|
96
106
|
children: "Tags"
|
|
97
|
-
}), /* @__PURE__ */
|
|
107
|
+
}), /* @__PURE__ */ c(a, {
|
|
98
108
|
id: "circle-tags",
|
|
99
|
-
value:
|
|
100
|
-
onChange:
|
|
109
|
+
value: H,
|
|
110
|
+
onChange: U,
|
|
101
111
|
placeholder: "Add tags…"
|
|
102
112
|
})]
|
|
103
113
|
}),
|
|
104
|
-
|
|
114
|
+
K && /* @__PURE__ */ c("p", {
|
|
105
115
|
className: "text-sm text-status-error-text",
|
|
106
|
-
children:
|
|
116
|
+
children: K
|
|
107
117
|
})
|
|
108
118
|
]
|
|
109
119
|
}),
|
|
110
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ l(g, { children: [/* @__PURE__ */ c(u, {
|
|
111
121
|
variant: "outline",
|
|
112
|
-
onClick: () =>
|
|
122
|
+
onClick: () => D(!1),
|
|
113
123
|
children: "Cancel"
|
|
114
|
-
}), /* @__PURE__ */
|
|
124
|
+
}), /* @__PURE__ */ c(u, {
|
|
115
125
|
onClick: async () => {
|
|
116
|
-
if (!
|
|
117
|
-
|
|
126
|
+
if (!A.trim()) {
|
|
127
|
+
q("Circle name is required.");
|
|
118
128
|
return;
|
|
119
129
|
}
|
|
120
|
-
if (!
|
|
121
|
-
|
|
130
|
+
if (!J) {
|
|
131
|
+
q("A URL slug is required.");
|
|
122
132
|
return;
|
|
123
133
|
}
|
|
124
134
|
try {
|
|
125
|
-
let e = await
|
|
126
|
-
name:
|
|
127
|
-
slug:
|
|
128
|
-
topic:
|
|
129
|
-
description:
|
|
130
|
-
visibility:
|
|
131
|
-
|
|
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.`),
|
|
144
|
+
n.success("Circle created", `${e.name} is ready.`), D(!1), Y(), O?.(e);
|
|
134
145
|
} catch (e) {
|
|
135
|
-
|
|
146
|
+
q(t(e));
|
|
136
147
|
}
|
|
137
148
|
},
|
|
138
|
-
disabled:
|
|
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 {
|
|
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":"
|
|
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 {
|
|
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
|
|
4
|
-
import { formatDateTime as
|
|
5
|
-
import { StatusBadge as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
|
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
|
|
18
|
-
let
|
|
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
|
|
21
|
-
eventId:
|
|
23
|
+
await C.mutateAsync({
|
|
24
|
+
eventId: x.id,
|
|
22
25
|
status: e
|
|
23
|
-
}),
|
|
26
|
+
}), T(e), r.success(`RSVP: ${e === "GOING" ? "Going" : "Interested"}`, x.title);
|
|
24
27
|
} catch (e) {
|
|
25
|
-
|
|
28
|
+
r.error("Could not RSVP", n(e));
|
|
26
29
|
}
|
|
27
|
-
},
|
|
28
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
35
|
+
/* @__PURE__ */ _("div", {
|
|
33
36
|
className: "flex items-start justify-between gap-3",
|
|
34
|
-
children: [/* @__PURE__ */
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
className: "mt-
|
|
43
|
-
children:
|
|
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__ */
|
|
52
|
+
/* @__PURE__ */ _("div", {
|
|
46
53
|
className: "mt-3 flex flex-col gap-1.5 text-sm text-text-secondary",
|
|
47
|
-
children: [/* @__PURE__ */
|
|
54
|
+
children: [/* @__PURE__ */ _("span", {
|
|
48
55
|
className: "inline-flex items-center gap-2",
|
|
49
56
|
children: [
|
|
50
|
-
/* @__PURE__ */
|
|
57
|
+
/* @__PURE__ */ g(u, { className: "h-4 w-4 text-text-tertiary" }),
|
|
51
58
|
" ",
|
|
52
|
-
|
|
59
|
+
i(x.startsAt)
|
|
53
60
|
]
|
|
54
|
-
}), /* @__PURE__ */
|
|
61
|
+
}), /* @__PURE__ */ _("span", {
|
|
55
62
|
className: "inline-flex items-center gap-2",
|
|
56
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 {
|
|
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,
|
|
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"}
|