@btst/stack 2.7.0 → 2.8.0

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 (181) hide show
  1. package/README.md +1 -0
  2. package/dist/packages/stack/src/plugins/blog/client/components/loading/post-navigation-skeleton.cjs +13 -0
  3. package/dist/packages/stack/src/plugins/blog/client/components/loading/post-navigation-skeleton.mjs +11 -0
  4. package/dist/packages/stack/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.cjs +17 -0
  5. package/dist/packages/stack/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.mjs +15 -0
  6. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +18 -7
  7. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +18 -7
  8. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-navigation.cjs +48 -52
  9. package/dist/packages/stack/src/plugins/blog/client/components/shared/post-navigation.mjs +49 -53
  10. package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.cjs +34 -37
  11. package/dist/packages/stack/src/plugins/blog/client/components/shared/recent-posts-carousel.mjs +35 -38
  12. package/dist/packages/stack/src/plugins/blog/client/hooks/blog-hooks.cjs +4 -21
  13. package/dist/packages/stack/src/plugins/blog/client/hooks/blog-hooks.mjs +4 -21
  14. package/dist/packages/stack/src/plugins/comments/api/getters.cjs +284 -0
  15. package/dist/packages/stack/src/plugins/comments/api/getters.mjs +280 -0
  16. package/dist/packages/stack/src/plugins/comments/api/mutations.cjs +118 -0
  17. package/dist/packages/stack/src/plugins/comments/api/mutations.mjs +112 -0
  18. package/dist/packages/stack/src/plugins/comments/api/plugin.cjs +335 -0
  19. package/dist/packages/stack/src/plugins/comments/api/plugin.mjs +333 -0
  20. package/dist/packages/stack/src/plugins/comments/api/query-key-defs.cjs +60 -0
  21. package/dist/packages/stack/src/plugins/comments/api/query-key-defs.mjs +55 -0
  22. package/dist/packages/stack/src/plugins/comments/api/serializers.cjs +23 -0
  23. package/dist/packages/stack/src/plugins/comments/api/serializers.mjs +21 -0
  24. package/dist/packages/stack/src/plugins/comments/client/components/comment-count.cjs +46 -0
  25. package/dist/packages/stack/src/plugins/comments/client/components/comment-count.mjs +44 -0
  26. package/dist/packages/stack/src/plugins/comments/client/components/comment-form.cjs +86 -0
  27. package/dist/packages/stack/src/plugins/comments/client/components/comment-form.mjs +84 -0
  28. package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.cjs +540 -0
  29. package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +538 -0
  30. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.cjs +64 -0
  31. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.cjs +426 -0
  32. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.internal.mjs +424 -0
  33. package/dist/packages/stack/src/plugins/comments/client/components/pages/moderation-page.mjs +62 -0
  34. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.cjs +66 -0
  35. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.cjs +256 -0
  36. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.internal.mjs +254 -0
  37. package/dist/packages/stack/src/plugins/comments/client/components/pages/my-comments-page.mjs +64 -0
  38. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.cjs +86 -0
  39. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.cjs +191 -0
  40. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.internal.mjs +189 -0
  41. package/dist/packages/stack/src/plugins/comments/client/components/pages/resource-comments-page.mjs +84 -0
  42. package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.cjs +27 -0
  43. package/dist/packages/stack/src/plugins/comments/client/components/shared/page-wrapper.mjs +25 -0
  44. package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.cjs +37 -0
  45. package/dist/packages/stack/src/plugins/comments/client/components/shared/pagination.mjs +35 -0
  46. package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.cjs +476 -0
  47. package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.mjs +464 -0
  48. package/dist/packages/stack/src/plugins/comments/client/localization/comments-moderation.cjs +67 -0
  49. package/dist/packages/stack/src/plugins/comments/client/localization/comments-moderation.mjs +65 -0
  50. package/dist/packages/stack/src/plugins/comments/client/localization/comments-my.cjs +27 -0
  51. package/dist/packages/stack/src/plugins/comments/client/localization/comments-my.mjs +25 -0
  52. package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.cjs +30 -0
  53. package/dist/packages/stack/src/plugins/comments/client/localization/comments-thread.mjs +28 -0
  54. package/dist/packages/stack/src/plugins/comments/client/localization/index.cjs +13 -0
  55. package/dist/packages/stack/src/plugins/comments/client/localization/index.mjs +11 -0
  56. package/dist/packages/stack/src/plugins/comments/client/plugin.cjs +116 -0
  57. package/dist/packages/stack/src/plugins/comments/client/plugin.mjs +114 -0
  58. package/dist/packages/stack/src/plugins/comments/client/utils.cjs +41 -0
  59. package/dist/packages/stack/src/plugins/comments/client/utils.mjs +37 -0
  60. package/dist/packages/stack/src/plugins/comments/db.cjs +75 -0
  61. package/dist/packages/stack/src/plugins/comments/db.mjs +73 -0
  62. package/dist/packages/stack/src/plugins/comments/schemas.cjs +45 -0
  63. package/dist/packages/stack/src/plugins/comments/schemas.mjs +38 -0
  64. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +0 -1
  65. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +0 -1
  66. package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.cjs +39 -22
  67. package/dist/packages/stack/src/plugins/kanban/client/components/pages/board-page.internal.mjs +40 -23
  68. package/dist/packages/ui/src/components/avatar.mjs +1 -1
  69. package/dist/packages/ui/src/components/pagination-controls.cjs +64 -0
  70. package/dist/packages/ui/src/components/pagination-controls.mjs +62 -0
  71. package/dist/packages/ui/src/components/when-visible.cjs +39 -0
  72. package/dist/packages/ui/src/components/when-visible.mjs +37 -0
  73. package/dist/plugins/blog/client/hooks/index.d.cts +1 -1
  74. package/dist/plugins/blog/client/hooks/index.d.mts +1 -1
  75. package/dist/plugins/blog/client/hooks/index.d.ts +1 -1
  76. package/dist/plugins/blog/client/index.d.cts +24 -2
  77. package/dist/plugins/blog/client/index.d.mts +24 -2
  78. package/dist/plugins/blog/client/index.d.ts +24 -2
  79. package/dist/plugins/comments/api/index.cjs +21 -0
  80. package/dist/plugins/comments/api/index.d.cts +126 -0
  81. package/dist/plugins/comments/api/index.d.mts +126 -0
  82. package/dist/plugins/comments/api/index.d.ts +126 -0
  83. package/dist/plugins/comments/api/index.mjs +5 -0
  84. package/dist/plugins/comments/client/components/index.cjs +15 -0
  85. package/dist/plugins/comments/client/components/index.d.cts +125 -0
  86. package/dist/plugins/comments/client/components/index.d.mts +125 -0
  87. package/dist/plugins/comments/client/components/index.d.ts +125 -0
  88. package/dist/plugins/comments/client/components/index.mjs +5 -0
  89. package/dist/plugins/comments/client/hooks/index.cjs +17 -0
  90. package/dist/plugins/comments/client/hooks/index.d.cts +200 -0
  91. package/dist/plugins/comments/client/hooks/index.d.mts +200 -0
  92. package/dist/plugins/comments/client/hooks/index.d.ts +200 -0
  93. package/dist/plugins/comments/client/hooks/index.mjs +1 -0
  94. package/dist/plugins/comments/client/index.cjs +9 -0
  95. package/dist/plugins/comments/client/index.d.cts +262 -0
  96. package/dist/plugins/comments/client/index.d.mts +262 -0
  97. package/dist/plugins/comments/client/index.d.ts +262 -0
  98. package/dist/plugins/comments/client/index.mjs +2 -0
  99. package/dist/plugins/comments/client.css +2 -0
  100. package/dist/plugins/comments/query-keys.cjs +113 -0
  101. package/dist/plugins/comments/query-keys.d.cts +71 -0
  102. package/dist/plugins/comments/query-keys.d.mts +71 -0
  103. package/dist/plugins/comments/query-keys.d.ts +71 -0
  104. package/dist/plugins/comments/query-keys.mjs +111 -0
  105. package/dist/plugins/comments/style.css +15 -0
  106. package/dist/plugins/kanban/api/index.d.cts +1 -1
  107. package/dist/plugins/kanban/api/index.d.mts +1 -1
  108. package/dist/plugins/kanban/api/index.d.ts +1 -1
  109. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  110. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  111. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  112. package/dist/plugins/kanban/client/index.d.cts +1 -1
  113. package/dist/plugins/kanban/client/index.d.mts +1 -1
  114. package/dist/plugins/kanban/client/index.d.ts +1 -1
  115. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  116. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  117. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  118. package/dist/shared/{stack.FeaWkglm.d.ts → stack.BxFl46lB.d.cts} +24 -1
  119. package/dist/shared/stack.C-b3Sn8j.d.cts +142 -0
  120. package/dist/shared/stack.C-b3Sn8j.d.mts +142 -0
  121. package/dist/shared/stack.C-b3Sn8j.d.ts +142 -0
  122. package/dist/shared/stack.CJE9sAjV.d.ts +335 -0
  123. package/dist/shared/stack.CmHRdhl8.d.cts +335 -0
  124. package/dist/shared/{stack.CNLHlv7r.d.mts → stack.DOZ1EXjM.d.mts} +6 -12
  125. package/dist/shared/{stack.FeaWkglm.d.mts → stack.DRpeDS6X.d.ts} +24 -1
  126. package/dist/shared/{stack.CQAZwXhV.d.cts → stack.DX-tQ93o.d.cts} +6 -12
  127. package/dist/shared/stack.Dcz6636A.d.mts +335 -0
  128. package/dist/shared/{stack.FeaWkglm.d.cts → stack.Jb0kQDJC.d.mts} +24 -1
  129. package/dist/shared/stack.Ldfkr5b2.d.cts +112 -0
  130. package/dist/shared/stack.Ldfkr5b2.d.mts +112 -0
  131. package/dist/shared/stack.Ldfkr5b2.d.ts +112 -0
  132. package/dist/shared/{stack.D3BsrpAz.d.ts → stack.VF6FhyZw.d.ts} +6 -12
  133. package/package.json +67 -2
  134. package/src/plugins/blog/client/components/loading/post-navigation-skeleton.tsx +10 -0
  135. package/src/plugins/blog/client/components/loading/recent-posts-carousel-skeleton.tsx +18 -0
  136. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +23 -8
  137. package/src/plugins/blog/client/components/shared/post-navigation.tsx +0 -5
  138. package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +1 -5
  139. package/src/plugins/blog/client/hooks/blog-hooks.tsx +8 -33
  140. package/src/plugins/blog/client/overrides.ts +26 -1
  141. package/src/plugins/cms/client/components/shared/pagination.tsx +14 -42
  142. package/src/plugins/comments/api/getters.ts +444 -0
  143. package/src/plugins/comments/api/index.ts +21 -0
  144. package/src/plugins/comments/api/mutations.ts +206 -0
  145. package/src/plugins/comments/api/plugin.ts +628 -0
  146. package/src/plugins/comments/api/query-key-defs.ts +143 -0
  147. package/src/plugins/comments/api/serializers.ts +37 -0
  148. package/src/plugins/comments/client/components/comment-count.tsx +66 -0
  149. package/src/plugins/comments/client/components/comment-form.tsx +112 -0
  150. package/src/plugins/comments/client/components/comment-thread.tsx +799 -0
  151. package/src/plugins/comments/client/components/index.tsx +11 -0
  152. package/src/plugins/comments/client/components/pages/moderation-page.internal.tsx +550 -0
  153. package/src/plugins/comments/client/components/pages/moderation-page.tsx +70 -0
  154. package/src/plugins/comments/client/components/pages/my-comments-page.internal.tsx +367 -0
  155. package/src/plugins/comments/client/components/pages/my-comments-page.tsx +72 -0
  156. package/src/plugins/comments/client/components/pages/resource-comments-page.internal.tsx +225 -0
  157. package/src/plugins/comments/client/components/pages/resource-comments-page.tsx +97 -0
  158. package/src/plugins/comments/client/components/shared/page-wrapper.tsx +32 -0
  159. package/src/plugins/comments/client/components/shared/pagination.tsx +44 -0
  160. package/src/plugins/comments/client/hooks/index.tsx +13 -0
  161. package/src/plugins/comments/client/hooks/use-comments.tsx +717 -0
  162. package/src/plugins/comments/client/index.ts +14 -0
  163. package/src/plugins/comments/client/localization/comments-moderation.ts +75 -0
  164. package/src/plugins/comments/client/localization/comments-my.ts +32 -0
  165. package/src/plugins/comments/client/localization/comments-thread.ts +32 -0
  166. package/src/plugins/comments/client/localization/index.ts +11 -0
  167. package/src/plugins/comments/client/overrides.ts +164 -0
  168. package/src/plugins/comments/client/plugin.tsx +195 -0
  169. package/src/plugins/comments/client/utils.ts +67 -0
  170. package/src/plugins/comments/client.css +2 -0
  171. package/src/plugins/comments/db.ts +77 -0
  172. package/src/plugins/comments/query-keys.ts +189 -0
  173. package/src/plugins/comments/schemas.ts +72 -0
  174. package/src/plugins/comments/style.css +15 -0
  175. package/src/plugins/comments/types.ts +73 -0
  176. package/src/plugins/kanban/client/components/forms/task-form.tsx +0 -1
  177. package/src/plugins/kanban/client/components/pages/board-page.internal.tsx +46 -27
  178. package/src/plugins/kanban/client/overrides.ts +27 -1
  179. package/dist/shared/{stack.Rtcvl8sS.d.cts → stack.BOokfhZD.d.cts} +3 -3
  180. package/dist/shared/{stack.D4Cea8II.d.ts → stack.BvCR4-9H.d.ts} +3 -3
  181. package/dist/shared/{stack.HE_IvqV5.d.mts → stack.CWxAl9K3.d.mts} +3 -3
@@ -0,0 +1,424 @@
1
+ "use client";
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../../../../../../ui/src/components/table.mjs';
5
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../../../../../ui/src/components/dialog.mjs';
6
+ import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '../../../../../../../ui/src/components/alert-dialog.mjs';
7
+ import { Button } from '../../../../../../../ui/src/components/button.mjs';
8
+ import { Badge } from '../../../../../../../ui/src/components/badge.mjs';
9
+ import { Tabs, TabsList, TabsTrigger } from '../../../../../../../ui/src/components/tabs.mjs';
10
+ import { Checkbox } from '../../../../../../../ui/src/components/checkbox.mjs';
11
+ import { Avatar, AvatarImage, AvatarFallback } from '../../../../../../../ui/src/components/avatar.mjs';
12
+ import { CheckCircle, Trash2, Eye, ShieldOff } from 'lucide-react';
13
+ import { toast } from 'sonner';
14
+ import { formatDistanceToNow } from 'date-fns';
15
+ import { useRegisterPageAIContext } from '@btst/stack/plugins/ai-chat/client/context';
16
+ import { useSuspenseModerationComments, useUpdateCommentStatus, useDeleteComment } from '../../hooks/use-comments.mjs';
17
+ import { COMMENTS_LOCALIZATION } from '../../localization/index.mjs';
18
+ import { getInitials } from '../../utils.mjs';
19
+ import { Pagination } from '../shared/pagination.mjs';
20
+
21
+ function StatusBadge({ status }) {
22
+ const variants = {
23
+ pending: "secondary",
24
+ approved: "default",
25
+ spam: "destructive"
26
+ };
27
+ return /* @__PURE__ */ jsx(Badge, { variant: variants[status], children: status });
28
+ }
29
+ function ModerationPage({
30
+ apiBaseURL,
31
+ apiBasePath,
32
+ headers,
33
+ localization: localizationProp
34
+ }) {
35
+ const loc = { ...COMMENTS_LOCALIZATION, ...localizationProp };
36
+ const [activeTab, setActiveTab] = useState("pending");
37
+ const [currentPage, setCurrentPage] = useState(1);
38
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
39
+ const [viewComment, setViewComment] = useState(
40
+ null
41
+ );
42
+ const [deleteIds, setDeleteIds] = useState([]);
43
+ const config = { apiBaseURL, apiBasePath, headers };
44
+ const { comments, total, limit, offset, totalPages, refetch } = useSuspenseModerationComments(config, {
45
+ status: activeTab,
46
+ page: currentPage
47
+ });
48
+ const updateStatus = useUpdateCommentStatus(config);
49
+ const deleteMutation = useDeleteComment(config);
50
+ useRegisterPageAIContext({
51
+ routeName: "comments-moderation",
52
+ pageDescription: `${total} ${activeTab} comments in the moderation queue.
53
+
54
+ Top ${activeTab} comments:
55
+ ${comments.slice(0, 5).map(
56
+ (c) => `- "${c.body.slice(0, 80)}${c.body.length > 80 ? "\u2026" : ""}" by ${c.resolvedAuthorName} on ${c.resourceType}/${c.resourceId}`
57
+ ).join("\n")}`,
58
+ suggestions: [
59
+ "Approve all safe-looking comments",
60
+ "Flag spam comments",
61
+ "Summarize today's discussion"
62
+ ]
63
+ });
64
+ const toggleSelect = (id) => {
65
+ setSelected((prev) => {
66
+ const next = new Set(prev);
67
+ next.has(id) ? next.delete(id) : next.add(id);
68
+ return next;
69
+ });
70
+ };
71
+ const toggleSelectAll = () => {
72
+ if (selected.size === comments.length) {
73
+ setSelected(/* @__PURE__ */ new Set());
74
+ } else {
75
+ setSelected(new Set(comments.map((c) => c.id)));
76
+ }
77
+ };
78
+ const handleApprove = async (id) => {
79
+ try {
80
+ await updateStatus.mutateAsync({ id, status: "approved" });
81
+ toast.success(loc.COMMENTS_MODERATION_TOAST_APPROVED);
82
+ await refetch();
83
+ } catch {
84
+ toast.error(loc.COMMENTS_MODERATION_TOAST_APPROVE_ERROR);
85
+ }
86
+ };
87
+ const handleSpam = async (id) => {
88
+ try {
89
+ await updateStatus.mutateAsync({ id, status: "spam" });
90
+ toast.success(loc.COMMENTS_MODERATION_TOAST_SPAM);
91
+ await refetch();
92
+ } catch {
93
+ toast.error(loc.COMMENTS_MODERATION_TOAST_SPAM_ERROR);
94
+ }
95
+ };
96
+ const handleDelete = async (ids) => {
97
+ try {
98
+ await Promise.all(ids.map((id) => deleteMutation.mutateAsync(id)));
99
+ toast.success(
100
+ ids.length === 1 ? loc.COMMENTS_MODERATION_TOAST_DELETED : loc.COMMENTS_MODERATION_TOAST_DELETED_PLURAL.replace(
101
+ "{n}",
102
+ String(ids.length)
103
+ )
104
+ );
105
+ setSelected(/* @__PURE__ */ new Set());
106
+ setDeleteIds([]);
107
+ await refetch();
108
+ } catch {
109
+ toast.error(loc.COMMENTS_MODERATION_TOAST_DELETE_ERROR);
110
+ }
111
+ };
112
+ const handleBulkApprove = async () => {
113
+ const ids = [...selected];
114
+ try {
115
+ await Promise.all(
116
+ ids.map((id) => updateStatus.mutateAsync({ id, status: "approved" }))
117
+ );
118
+ toast.success(
119
+ loc.COMMENTS_MODERATION_TOAST_BULK_APPROVED.replace(
120
+ "{n}",
121
+ String(ids.length)
122
+ )
123
+ );
124
+ setSelected(/* @__PURE__ */ new Set());
125
+ await refetch();
126
+ } catch {
127
+ toast.error(loc.COMMENTS_MODERATION_TOAST_BULK_APPROVE_ERROR);
128
+ }
129
+ };
130
+ return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-5xl space-y-6", "data-testid": "moderation-page", children: [
131
+ /* @__PURE__ */ jsxs("div", { children: [
132
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold", children: loc.COMMENTS_MODERATION_TITLE }),
133
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm mt-1", children: loc.COMMENTS_MODERATION_DESCRIPTION })
134
+ ] }),
135
+ /* @__PURE__ */ jsx(
136
+ Tabs,
137
+ {
138
+ value: activeTab,
139
+ onValueChange: (v) => {
140
+ setActiveTab(v);
141
+ setCurrentPage(1);
142
+ setSelected(/* @__PURE__ */ new Set());
143
+ },
144
+ children: /* @__PURE__ */ jsxs(TabsList, { children: [
145
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "pending", "data-testid": "tab-pending", children: loc.COMMENTS_MODERATION_TAB_PENDING }),
146
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "approved", "data-testid": "tab-approved", children: loc.COMMENTS_MODERATION_TAB_APPROVED }),
147
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "spam", "data-testid": "tab-spam", children: loc.COMMENTS_MODERATION_TAB_SPAM })
148
+ ] })
149
+ }
150
+ ),
151
+ selected.size > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 bg-muted rounded-lg", children: [
152
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: loc.COMMENTS_MODERATION_SELECTED.replace(
153
+ "{n}",
154
+ String(selected.size)
155
+ ) }),
156
+ activeTab !== "approved" && /* @__PURE__ */ jsxs(
157
+ Button,
158
+ {
159
+ size: "sm",
160
+ variant: "outline",
161
+ onClick: handleBulkApprove,
162
+ disabled: updateStatus.isPending,
163
+ children: [
164
+ /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 mr-1" }),
165
+ loc.COMMENTS_MODERATION_APPROVE_SELECTED
166
+ ]
167
+ }
168
+ ),
169
+ /* @__PURE__ */ jsxs(
170
+ Button,
171
+ {
172
+ size: "sm",
173
+ variant: "outline",
174
+ className: "text-destructive border-destructive hover:bg-destructive hover:text-destructive-foreground",
175
+ onClick: () => setDeleteIds([...selected]),
176
+ children: [
177
+ /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 mr-1" }),
178
+ loc.COMMENTS_MODERATION_DELETE_SELECTED
179
+ ]
180
+ }
181
+ )
182
+ ] }),
183
+ comments.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-16 text-muted-foreground", children: [
184
+ /* @__PURE__ */ jsx(CheckCircle, { className: "h-8 w-8" }),
185
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: loc.COMMENTS_MODERATION_EMPTY.replace("{status}", activeTab) })
186
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
187
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs(Table, { children: [
188
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
189
+ /* @__PURE__ */ jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsx(
190
+ Checkbox,
191
+ {
192
+ checked: selected.size === comments.length && comments.length > 0,
193
+ onCheckedChange: toggleSelectAll,
194
+ "aria-label": loc.COMMENTS_MODERATION_SELECT_ALL
195
+ }
196
+ ) }),
197
+ /* @__PURE__ */ jsx(TableHead, { children: loc.COMMENTS_MODERATION_COL_AUTHOR }),
198
+ /* @__PURE__ */ jsx(TableHead, { children: loc.COMMENTS_MODERATION_COL_COMMENT }),
199
+ /* @__PURE__ */ jsx(TableHead, { children: loc.COMMENTS_MODERATION_COL_RESOURCE }),
200
+ /* @__PURE__ */ jsx(TableHead, { children: loc.COMMENTS_MODERATION_COL_DATE }),
201
+ /* @__PURE__ */ jsx(TableHead, { className: "w-36", children: loc.COMMENTS_MODERATION_COL_ACTIONS })
202
+ ] }) }),
203
+ /* @__PURE__ */ jsx(TableBody, { children: comments.map((comment) => /* @__PURE__ */ jsxs(
204
+ TableRow,
205
+ {
206
+ "data-testid": "moderation-row",
207
+ "data-comment-id": comment.id,
208
+ children: [
209
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
210
+ Checkbox,
211
+ {
212
+ checked: selected.has(comment.id),
213
+ onCheckedChange: () => toggleSelect(comment.id),
214
+ "aria-label": loc.COMMENTS_MODERATION_SELECT_ONE
215
+ }
216
+ ) }),
217
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
218
+ /* @__PURE__ */ jsxs(Avatar, { className: "h-7 w-7", children: [
219
+ comment.resolvedAvatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: comment.resolvedAvatarUrl }),
220
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "text-xs", children: getInitials(comment.resolvedAuthorName) })
221
+ ] }),
222
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate max-w-[100px]", children: comment.resolvedAuthorName })
223
+ ] }) }),
224
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground max-w-xs truncate", children: comment.body }) }),
225
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
226
+ comment.resourceType,
227
+ "/",
228
+ comment.resourceId
229
+ ] }) }),
230
+ /* @__PURE__ */ jsx(TableCell, { className: "text-xs text-muted-foreground whitespace-nowrap", children: formatDistanceToNow(new Date(comment.createdAt), {
231
+ addSuffix: true
232
+ }) }),
233
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
234
+ /* @__PURE__ */ jsx(
235
+ Button,
236
+ {
237
+ variant: "ghost",
238
+ size: "icon",
239
+ className: "h-7 w-7",
240
+ title: loc.COMMENTS_MODERATION_ACTION_VIEW,
241
+ onClick: () => setViewComment(comment),
242
+ "data-testid": "view-button",
243
+ children: /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" })
244
+ }
245
+ ),
246
+ activeTab !== "approved" && /* @__PURE__ */ jsx(
247
+ Button,
248
+ {
249
+ variant: "ghost",
250
+ size: "icon",
251
+ className: "h-7 w-7 text-green-600 hover:text-green-700",
252
+ title: loc.COMMENTS_MODERATION_ACTION_APPROVE,
253
+ onClick: () => handleApprove(comment.id),
254
+ disabled: updateStatus.isPending,
255
+ "data-testid": "approve-button",
256
+ children: /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4" })
257
+ }
258
+ ),
259
+ activeTab !== "spam" && /* @__PURE__ */ jsx(
260
+ Button,
261
+ {
262
+ variant: "ghost",
263
+ size: "icon",
264
+ className: "h-7 w-7 text-orange-500 hover:text-orange-600",
265
+ title: loc.COMMENTS_MODERATION_ACTION_SPAM,
266
+ onClick: () => handleSpam(comment.id),
267
+ disabled: updateStatus.isPending,
268
+ "data-testid": "spam-button",
269
+ children: /* @__PURE__ */ jsx(ShieldOff, { className: "h-4 w-4" })
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx(
273
+ Button,
274
+ {
275
+ variant: "ghost",
276
+ size: "icon",
277
+ className: "h-7 w-7 text-destructive hover:text-destructive",
278
+ title: loc.COMMENTS_MODERATION_ACTION_DELETE,
279
+ onClick: () => setDeleteIds([comment.id]),
280
+ "data-testid": "delete-button",
281
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
282
+ }
283
+ )
284
+ ] }) })
285
+ ]
286
+ },
287
+ comment.id
288
+ )) })
289
+ ] }) }),
290
+ /* @__PURE__ */ jsx(
291
+ Pagination,
292
+ {
293
+ currentPage,
294
+ totalPages,
295
+ onPageChange: setCurrentPage,
296
+ total,
297
+ limit,
298
+ offset
299
+ }
300
+ )
301
+ ] }),
302
+ /* @__PURE__ */ jsx(Dialog, { open: !!viewComment, onOpenChange: () => setViewComment(null), children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-2xl", children: [
303
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: loc.COMMENTS_MODERATION_DIALOG_TITLE }) }),
304
+ viewComment && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
305
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
306
+ /* @__PURE__ */ jsxs(Avatar, { className: "h-10 w-10", children: [
307
+ viewComment.resolvedAvatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: viewComment.resolvedAvatarUrl }),
308
+ /* @__PURE__ */ jsx(AvatarFallback, { children: getInitials(viewComment.resolvedAuthorName) })
309
+ ] }),
310
+ /* @__PURE__ */ jsxs("div", { children: [
311
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-sm", children: viewComment.resolvedAuthorName }),
312
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: new Date(viewComment.createdAt).toLocaleString() })
313
+ ] }),
314
+ /* @__PURE__ */ jsx(StatusBadge, { status: viewComment.status })
315
+ ] }),
316
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 text-sm", children: [
317
+ /* @__PURE__ */ jsxs("div", { children: [
318
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: loc.COMMENTS_MODERATION_DIALOG_RESOURCE }),
319
+ /* @__PURE__ */ jsxs("p", { className: "font-mono text-xs", children: [
320
+ viewComment.resourceType,
321
+ "/",
322
+ viewComment.resourceId
323
+ ] })
324
+ ] }),
325
+ /* @__PURE__ */ jsxs("div", { children: [
326
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: loc.COMMENTS_MODERATION_DIALOG_LIKES }),
327
+ /* @__PURE__ */ jsx("p", { children: viewComment.likes })
328
+ ] }),
329
+ viewComment.parentId && /* @__PURE__ */ jsxs("div", { children: [
330
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: loc.COMMENTS_MODERATION_DIALOG_REPLY_TO }),
331
+ /* @__PURE__ */ jsx("p", { className: "font-mono text-xs", children: viewComment.parentId })
332
+ ] }),
333
+ viewComment.editedAt && /* @__PURE__ */ jsxs("div", { children: [
334
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: loc.COMMENTS_MODERATION_DIALOG_EDITED }),
335
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: new Date(viewComment.editedAt).toLocaleString() })
336
+ ] })
337
+ ] }),
338
+ /* @__PURE__ */ jsxs("div", { children: [
339
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs mb-1", children: loc.COMMENTS_MODERATION_DIALOG_BODY }),
340
+ /* @__PURE__ */ jsx("div", { className: "p-3 bg-muted rounded-lg text-sm whitespace-pre-wrap break-words", children: viewComment.body })
341
+ ] }),
342
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
343
+ viewComment.status !== "approved" && /* @__PURE__ */ jsxs(
344
+ Button,
345
+ {
346
+ size: "sm",
347
+ onClick: async () => {
348
+ await handleApprove(viewComment.id);
349
+ setViewComment(null);
350
+ },
351
+ disabled: updateStatus.isPending,
352
+ "data-testid": "dialog-approve-button",
353
+ children: [
354
+ /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 mr-1" }),
355
+ loc.COMMENTS_MODERATION_DIALOG_APPROVE
356
+ ]
357
+ }
358
+ ),
359
+ viewComment.status !== "spam" && /* @__PURE__ */ jsxs(
360
+ Button,
361
+ {
362
+ size: "sm",
363
+ variant: "outline",
364
+ onClick: async () => {
365
+ await handleSpam(viewComment.id);
366
+ setViewComment(null);
367
+ },
368
+ disabled: updateStatus.isPending,
369
+ children: [
370
+ /* @__PURE__ */ jsx(ShieldOff, { className: "h-4 w-4 mr-1" }),
371
+ loc.COMMENTS_MODERATION_DIALOG_MARK_SPAM
372
+ ]
373
+ }
374
+ ),
375
+ /* @__PURE__ */ jsxs(
376
+ Button,
377
+ {
378
+ size: "sm",
379
+ variant: "destructive",
380
+ onClick: () => {
381
+ setDeleteIds([viewComment.id]);
382
+ setViewComment(null);
383
+ },
384
+ children: [
385
+ /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 mr-1" }),
386
+ loc.COMMENTS_MODERATION_DIALOG_DELETE
387
+ ]
388
+ }
389
+ )
390
+ ] })
391
+ ] })
392
+ ] }) }),
393
+ /* @__PURE__ */ jsx(
394
+ AlertDialog,
395
+ {
396
+ open: deleteIds.length > 0,
397
+ onOpenChange: (open) => !open && setDeleteIds([]),
398
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
399
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
400
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: deleteIds.length === 1 ? loc.COMMENTS_MODERATION_DELETE_TITLE_SINGULAR : loc.COMMENTS_MODERATION_DELETE_TITLE_PLURAL.replace(
401
+ "{n}",
402
+ String(deleteIds.length)
403
+ ) }),
404
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: deleteIds.length === 1 ? loc.COMMENTS_MODERATION_DELETE_DESCRIPTION_SINGULAR : loc.COMMENTS_MODERATION_DELETE_DESCRIPTION_PLURAL })
405
+ ] }),
406
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
407
+ /* @__PURE__ */ jsx(AlertDialogCancel, { children: loc.COMMENTS_MODERATION_DELETE_CANCEL }),
408
+ /* @__PURE__ */ jsx(
409
+ AlertDialogAction,
410
+ {
411
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
412
+ onClick: () => handleDelete(deleteIds),
413
+ "data-testid": "confirm-delete-button",
414
+ children: deleteMutation.isPending ? loc.COMMENTS_MODERATION_DELETE_DELETING : loc.COMMENTS_MODERATION_DELETE_CONFIRM
415
+ }
416
+ )
417
+ ] })
418
+ ] })
419
+ }
420
+ )
421
+ ] });
422
+ }
423
+
424
+ export { ModerationPage };
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { lazy } from 'react';
4
+ import { ComposedRoute } from '@btst/stack/client/components';
5
+ import { usePluginOverrides } from '@btst/stack/context';
6
+ import { COMMENTS_LOCALIZATION } from '../../localization/index.mjs';
7
+ import { useRouteLifecycle } from '../../../../../../../ui/src/hooks/use-route-lifecycle.mjs';
8
+ import { PageWrapper } from '../shared/page-wrapper.mjs';
9
+
10
+ const ModerationPageInternal = lazy(
11
+ () => import('./moderation-page.internal.mjs').then((m) => ({
12
+ default: m.ModerationPage
13
+ }))
14
+ );
15
+ function ModerationPageSkeleton() {
16
+ return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-5xl mx-auto space-y-4 animate-pulse", children: [
17
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-64 rounded bg-muted" }),
18
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-48 rounded bg-muted" }),
19
+ /* @__PURE__ */ jsx("div", { className: "h-10 w-72 rounded bg-muted" }),
20
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border h-64 bg-muted" })
21
+ ] });
22
+ }
23
+ function ModerationPageComponent() {
24
+ return /* @__PURE__ */ jsx(
25
+ ComposedRoute,
26
+ {
27
+ path: "/comments/moderation",
28
+ PageComponent: ModerationPageWrapper,
29
+ LoadingComponent: ModerationPageSkeleton,
30
+ onError: (error) => console.error("[btst/comments] Moderation error:", error)
31
+ }
32
+ );
33
+ }
34
+ function ModerationPageWrapper() {
35
+ const overrides = usePluginOverrides("comments");
36
+ const loc = { ...COMMENTS_LOCALIZATION, ...overrides.localization };
37
+ useRouteLifecycle({
38
+ routeName: "moderation",
39
+ context: {
40
+ path: "/comments/moderation",
41
+ isSSR: typeof window === "undefined"
42
+ },
43
+ overrides,
44
+ beforeRenderHook: (o, context) => {
45
+ if (o.onBeforeModerationPageRendered) {
46
+ return o.onBeforeModerationPageRendered(context);
47
+ }
48
+ return true;
49
+ }
50
+ });
51
+ return /* @__PURE__ */ jsx(PageWrapper, { children: /* @__PURE__ */ jsx(
52
+ ModerationPageInternal,
53
+ {
54
+ apiBaseURL: overrides.apiBaseURL,
55
+ apiBasePath: overrides.apiBasePath,
56
+ headers: overrides.headers,
57
+ localization: loc
58
+ }
59
+ ) });
60
+ }
61
+
62
+ export { ModerationPageComponent };
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const components = require('@btst/stack/client/components');
7
+ const context = require('@btst/stack/context');
8
+ const index = require('../../localization/index.cjs');
9
+ const useRouteLifecycle = require('../../../../../../../ui/src/hooks/use-route-lifecycle.cjs');
10
+ const pageWrapper = require('../shared/page-wrapper.cjs');
11
+
12
+ const UserCommentsPageInternal = React.lazy(
13
+ () => import('./my-comments-page.internal.cjs').then((m) => ({
14
+ default: m.UserCommentsPage
15
+ }))
16
+ );
17
+ function UserCommentsPageSkeleton() {
18
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-3xl mx-auto space-y-4 animate-pulse", children: [
19
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-48 rounded bg-muted" }),
20
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-64 rounded bg-muted" }),
21
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border h-96 bg-muted" })
22
+ ] });
23
+ }
24
+ function UserCommentsPageComponent() {
25
+ return /* @__PURE__ */ jsxRuntime.jsx(
26
+ components.ComposedRoute,
27
+ {
28
+ path: "/comments",
29
+ PageComponent: UserCommentsPageWrapper,
30
+ LoadingComponent: UserCommentsPageSkeleton,
31
+ onError: (error) => console.error("[btst/comments] User Comments error:", error)
32
+ }
33
+ );
34
+ }
35
+ function UserCommentsPageWrapper() {
36
+ const overrides = context.usePluginOverrides("comments");
37
+ const loc = { ...index.COMMENTS_LOCALIZATION, ...overrides.localization };
38
+ useRouteLifecycle.useRouteLifecycle({
39
+ routeName: "userComments",
40
+ context: {
41
+ path: "/comments",
42
+ isSSR: typeof window === "undefined"
43
+ },
44
+ overrides,
45
+ beforeRenderHook: (o, context) => {
46
+ if (o.onBeforeUserCommentsPageRendered) {
47
+ const result = o.onBeforeUserCommentsPageRendered(context);
48
+ return result === false ? false : true;
49
+ }
50
+ return true;
51
+ }
52
+ });
53
+ return /* @__PURE__ */ jsxRuntime.jsx(pageWrapper.PageWrapper, { children: /* @__PURE__ */ jsxRuntime.jsx(
54
+ UserCommentsPageInternal,
55
+ {
56
+ apiBaseURL: overrides.apiBaseURL,
57
+ apiBasePath: overrides.apiBasePath,
58
+ headers: overrides.headers,
59
+ currentUserId: overrides.currentUserId,
60
+ resourceLinks: overrides.resourceLinks,
61
+ localization: loc
62
+ }
63
+ ) });
64
+ }
65
+
66
+ exports.UserCommentsPageComponent = UserCommentsPageComponent;