medusa-plugin-ratings 0.1.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 (31) hide show
  1. package/.medusa/server/medusa-config.d.ts +1 -0
  2. package/.medusa/server/medusa-config.js +23 -0
  3. package/.medusa/server/src/admin/index.js +490 -0
  4. package/.medusa/server/src/admin/index.mjs +489 -0
  5. package/.medusa/server/src/api/admin/reviews/[id]/route.d.ts +5 -0
  6. package/.medusa/server/src/api/admin/reviews/[id]/route.js +48 -0
  7. package/.medusa/server/src/api/admin/reviews/approve/route.d.ts +3 -0
  8. package/.medusa/server/src/api/admin/reviews/approve/route.js +25 -0
  9. package/.medusa/server/src/api/admin/reviews/reject/route.d.ts +3 -0
  10. package/.medusa/server/src/api/admin/reviews/reject/route.js +25 -0
  11. package/.medusa/server/src/api/admin/reviews/route.d.ts +4 -0
  12. package/.medusa/server/src/api/admin/reviews/route.js +34 -0
  13. package/.medusa/server/src/api/middlewares.d.ts +2 -0
  14. package/.medusa/server/src/api/middlewares.js +84 -0
  15. package/.medusa/server/src/api/store/reviews/[productId]/route.d.ts +4 -0
  16. package/.medusa/server/src/api/store/reviews/[productId]/route.js +85 -0
  17. package/.medusa/server/src/api/validators.d.ts +144 -0
  18. package/.medusa/server/src/api/validators.js +38 -0
  19. package/.medusa/server/src/modules/review/index.d.ts +36 -0
  20. package/.medusa/server/src/modules/review/index.js +25 -0
  21. package/.medusa/server/src/modules/review/migrations/Migration20260409183947.d.ts +5 -0
  22. package/.medusa/server/src/modules/review/migrations/Migration20260409183947.js +22 -0
  23. package/.medusa/server/src/modules/review/models/review-activity.d.ts +27 -0
  24. package/.medusa/server/src/modules/review/models/review-activity.js +21 -0
  25. package/.medusa/server/src/modules/review/models/review.d.ts +26 -0
  26. package/.medusa/server/src/modules/review/models/review.js +30 -0
  27. package/.medusa/server/src/modules/review/service.d.ts +139 -0
  28. package/.medusa/server/src/modules/review/service.js +46 -0
  29. package/LICENSE +21 -0
  30. package/README.md +51 -0
  31. package/package.json +88 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@medusajs/framework/utils");
4
+ (0, utils_1.loadEnv)(process.env.NODE_ENV || 'development', process.cwd());
5
+ module.exports = (0, utils_1.defineConfig)({
6
+ projectConfig: {
7
+ redisUrl: process.env.REDIS_URL,
8
+ databaseUrl: process.env.DATABASE_URL,
9
+ http: {
10
+ storeCors: process.env.STORE_CORS || 'http://localhost:5173',
11
+ adminCors: process.env.ADMIN_CORS || 'http://localhost:5173,http://localhost:9000',
12
+ authCors: process.env.AUTH_CORS || 'http://localhost:5173,http://localhost:9000',
13
+ jwtSecret: process.env.JWT_SECRET || 'supersecret',
14
+ cookieSecret: process.env.COOKIE_SECRET || 'supersecret'
15
+ }
16
+ },
17
+ modules: [
18
+ {
19
+ resolve: './src/modules/review'
20
+ }
21
+ ]
22
+ });
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVkdXNhLWNvbmZpZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL21lZHVzYS1jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxREFBaUU7QUFFakUsSUFBQSxlQUFPLEVBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLElBQUksYUFBYSxFQUFFLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFBO0FBRTdELE1BQU0sQ0FBQyxPQUFPLEdBQUcsSUFBQSxvQkFBWSxFQUFDO0lBQzdCLGFBQWEsRUFBRTtRQUNkLFFBQVEsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVM7UUFDL0IsV0FBVyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWTtRQUNyQyxJQUFJLEVBQUU7WUFDTCxTQUFTLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksdUJBQXVCO1lBQzVELFNBQVMsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsSUFBSSw2Q0FBNkM7WUFDbEYsUUFBUSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxJQUFJLDZDQUE2QztZQUNoRixTQUFTLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksYUFBYTtZQUNsRCxZQUFZLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLElBQUksYUFBYTtTQUN4RDtLQUNEO0lBQ0QsT0FBTyxFQUFFO1FBQ1I7WUFDQyxPQUFPLEVBQUUsc0JBQXNCO1NBQy9CO0tBQ0Q7Q0FDRCxDQUFDLENBQUEifQ==
@@ -0,0 +1,490 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const react = require("react");
4
+ const reactRouterDom = require("react-router-dom");
5
+ const adminSdk = require("@medusajs/admin-sdk");
6
+ const icons = require("@medusajs/icons");
7
+ const ui = require("@medusajs/ui");
8
+ const reactQuery = require("@tanstack/react-query");
9
+ const Medusa = require("@medusajs/js-sdk");
10
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
+ const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
12
+ const sdk = new Medusa__default.default({
13
+ baseUrl: "/",
14
+ debug: false,
15
+ auth: {
16
+ type: "session"
17
+ }
18
+ });
19
+ const useReviewsList = (params) => {
20
+ return reactQuery.useQuery({
21
+ queryFn: () => sdk.client.fetch("/admin/reviews", { query: params }),
22
+ queryKey: ["reviews", params]
23
+ });
24
+ };
25
+ const useReview = (id) => {
26
+ return reactQuery.useQuery({
27
+ queryFn: () => sdk.client.fetch(`/admin/reviews/${id}`),
28
+ queryKey: ["review", id],
29
+ enabled: !!id
30
+ });
31
+ };
32
+ const useUpdateReview = (id) => {
33
+ const queryClient = reactQuery.useQueryClient();
34
+ return reactQuery.useMutation({
35
+ mutationFn: (data) => sdk.client.fetch(`/admin/reviews/${id}`, {
36
+ method: "POST",
37
+ body: data
38
+ }),
39
+ onSuccess: () => {
40
+ queryClient.invalidateQueries({ queryKey: ["reviews"] });
41
+ queryClient.invalidateQueries({ queryKey: ["review", id] });
42
+ }
43
+ });
44
+ };
45
+ const useDeleteReview = () => {
46
+ const queryClient = reactQuery.useQueryClient();
47
+ return reactQuery.useMutation({
48
+ mutationFn: (id) => sdk.client.fetch(`/admin/reviews/${id}`, { method: "DELETE" }),
49
+ onSuccess: () => {
50
+ queryClient.invalidateQueries({ queryKey: ["reviews"] });
51
+ }
52
+ });
53
+ };
54
+ const useDeleteReviews = () => {
55
+ const queryClient = reactQuery.useQueryClient();
56
+ return reactQuery.useMutation({
57
+ mutationFn: (ids) => sdk.client.fetch("/admin/reviews", { method: "DELETE", body: { ids } }),
58
+ onSuccess: () => {
59
+ queryClient.invalidateQueries({ queryKey: ["reviews"] });
60
+ }
61
+ });
62
+ };
63
+ const useApproveReviews = () => {
64
+ const queryClient = reactQuery.useQueryClient();
65
+ return reactQuery.useMutation({
66
+ mutationFn: (ids) => sdk.client.fetch("/admin/reviews/approve", { method: "POST", body: { ids } }),
67
+ onSuccess: () => {
68
+ queryClient.invalidateQueries({ queryKey: ["reviews"] });
69
+ }
70
+ });
71
+ };
72
+ const useRejectReviews = () => {
73
+ const queryClient = reactQuery.useQueryClient();
74
+ return reactQuery.useMutation({
75
+ mutationFn: (ids) => sdk.client.fetch("/admin/reviews/reject", { method: "POST", body: { ids } }),
76
+ onSuccess: () => {
77
+ queryClient.invalidateQueries({ queryKey: ["reviews"] });
78
+ }
79
+ });
80
+ };
81
+ const config = adminSdk.defineRouteConfig({ label: "Reviews", icon: icons.Star, rank: 10 });
82
+ const handle$1 = { breadcrumb: () => "Reviews" };
83
+ const STATUS_COLORS$1 = {
84
+ pending: "blue",
85
+ approved: "green",
86
+ rejected: "red"
87
+ };
88
+ const STATUS_LABELS$1 = {
89
+ pending: "Pending",
90
+ approved: "Approved",
91
+ rejected: "Rejected"
92
+ };
93
+ const ReviewsPage = () => {
94
+ const limit = 20;
95
+ const [pagination, setPagination] = react.useState({
96
+ pageSize: limit,
97
+ pageIndex: 0
98
+ });
99
+ const offset = react.useMemo(() => pagination.pageIndex * limit, [pagination]);
100
+ const [filtering, setFiltering] = react.useState({ status: "pending" });
101
+ const [sorting, setSorting] = react.useState(null);
102
+ const [search, setSearch] = react.useState("");
103
+ const [rowSelection, setRowSelection] = react.useState({});
104
+ const { data, isLoading } = useReviewsList({
105
+ limit,
106
+ offset,
107
+ q: search || void 0,
108
+ ...filtering.status !== void 0 ? { status: filtering.status } : {},
109
+ order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : "-created_at"
110
+ });
111
+ const { mutateAsync: approveReviews } = useApproveReviews();
112
+ const { mutateAsync: rejectReviews } = useRejectReviews();
113
+ const { mutateAsync: deleteReviews } = useDeleteReviews();
114
+ const columnHelper = ui.createDataTableColumnHelper();
115
+ const columns = [
116
+ columnHelper.select(),
117
+ columnHelper.accessor("status", {
118
+ header: "Status",
119
+ cell: ({ getValue }) => {
120
+ const s = getValue();
121
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: STATUS_COLORS$1[s], children: STATUS_LABELS$1[s] });
122
+ }
123
+ }),
124
+ columnHelper.accessor("rating", {
125
+ header: "Rating",
126
+ cell: ({ getValue }) => `${getValue()} / 5`
127
+ }),
128
+ columnHelper.accessor("author_name", {
129
+ header: "Author",
130
+ enableSorting: true,
131
+ sortLabel: "Author",
132
+ sortAscLabel: "A–Z",
133
+ sortDescLabel: "Z–A"
134
+ }),
135
+ columnHelper.accessor("title", {
136
+ header: "Title",
137
+ cell: ({ getValue }) => {
138
+ const v = getValue();
139
+ return v ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "truncate max-w-[220px]", children: v }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: "—" });
140
+ }
141
+ }),
142
+ columnHelper.accessor("body", {
143
+ header: "Preview",
144
+ cell: ({ getValue }) => {
145
+ const v = getValue();
146
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle truncate max-w-[280px]", children: v.length > 60 ? `${v.slice(0, 60)}…` : v });
147
+ }
148
+ }),
149
+ columnHelper.accessor("created_at", {
150
+ header: "Received",
151
+ enableSorting: true,
152
+ sortLabel: "Received",
153
+ sortAscLabel: "Oldest first",
154
+ sortDescLabel: "Newest first",
155
+ cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: new Date(getValue()).toLocaleDateString() })
156
+ })
157
+ ];
158
+ const commandHelper = ui.createDataTableCommandHelper();
159
+ const prompt = ui.usePrompt();
160
+ const useCommands = () => [
161
+ commandHelper.command({
162
+ label: "Approve",
163
+ shortcut: "A",
164
+ action: async (selection) => {
165
+ const ids = Object.keys(selection);
166
+ const confirmed = await prompt({
167
+ title: `Approve ${ids.length} review${ids.length > 1 ? "s" : ""}?`,
168
+ description: "Approved reviews will be visible to customers.",
169
+ confirmText: "Approve",
170
+ cancelText: "Cancel",
171
+ variant: "confirmation"
172
+ });
173
+ if (!confirmed) return;
174
+ await approveReviews(ids);
175
+ setRowSelection({});
176
+ }
177
+ }),
178
+ commandHelper.command({
179
+ label: "Reject",
180
+ shortcut: "R",
181
+ action: async (selection) => {
182
+ const ids = Object.keys(selection);
183
+ const confirmed = await prompt({
184
+ title: `Reject ${ids.length} review${ids.length > 1 ? "s" : ""}?`,
185
+ description: "Rejected reviews will not be shown to customers.",
186
+ confirmText: "Reject",
187
+ cancelText: "Cancel",
188
+ variant: "confirmation"
189
+ });
190
+ if (!confirmed) return;
191
+ await rejectReviews(ids);
192
+ setRowSelection({});
193
+ }
194
+ }),
195
+ commandHelper.command({
196
+ label: "Delete",
197
+ shortcut: "X",
198
+ action: async (selection) => {
199
+ const ids = Object.keys(selection);
200
+ const confirmed = await prompt({
201
+ title: `Delete ${ids.length} review${ids.length > 1 ? "s" : ""}?`,
202
+ description: "This cannot be undone.",
203
+ confirmText: "Delete",
204
+ cancelText: "Cancel",
205
+ variant: "danger"
206
+ });
207
+ if (!confirmed) return;
208
+ await deleteReviews(ids);
209
+ setRowSelection({});
210
+ }
211
+ })
212
+ ];
213
+ const commands = useCommands();
214
+ const filterHelper = ui.createDataTableFilterHelper();
215
+ const filters = [
216
+ filterHelper.accessor("status", {
217
+ type: "select",
218
+ label: "Status",
219
+ options: [
220
+ { label: "Pending", value: "pending" },
221
+ { label: "Approved", value: "approved" },
222
+ { label: "Rejected", value: "rejected" }
223
+ ]
224
+ })
225
+ ];
226
+ const navigate = reactRouterDom.useNavigate();
227
+ const table = ui.useDataTable({
228
+ columns,
229
+ data: (data == null ? void 0 : data.reviews) || [],
230
+ getRowId: (row) => row.id,
231
+ rowCount: (data == null ? void 0 : data.count) || 0,
232
+ isLoading,
233
+ commands,
234
+ filters,
235
+ rowSelection: { state: rowSelection, onRowSelectionChange: setRowSelection },
236
+ onRowClick: (_, row) => navigate(`${row.id}`),
237
+ pagination: { state: pagination, onPaginationChange: setPagination },
238
+ filtering: { state: filtering, onFilteringChange: setFiltering },
239
+ sorting: { state: sorting, onSortingChange: setSorting },
240
+ search: { state: search, onSearchChange: setSearch }
241
+ });
242
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-4 p-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
243
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable.Toolbar, { className: "flex flex-col items-start justify-between gap-2 md:flex-row md:items-center", children: [
244
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: "Reviews" }),
245
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
246
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Search, { placeholder: "Search by author…" }),
247
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.FilterMenu, { tooltip: "Filter" })
248
+ ] })
249
+ ] }),
250
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}),
251
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.CommandBar, { selectedLabel: (count) => `${count} selected` }),
252
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
253
+ ] }) }) });
254
+ };
255
+ const ActionMenu = ({ groups }) => {
256
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
257
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
258
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Content, { children: groups.map((group, index) => {
259
+ if (!group.actions.length) {
260
+ return null;
261
+ }
262
+ const isLast = index === groups.length - 1;
263
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Group, { children: [
264
+ group.actions.map((action, index2) => {
265
+ if (action.onClick) {
266
+ return /* @__PURE__ */ jsxRuntime.jsxs(
267
+ ui.DropdownMenu.Item,
268
+ {
269
+ disabled: action.disabled,
270
+ onClick: (e) => {
271
+ e.stopPropagation();
272
+ action.onClick();
273
+ },
274
+ className: ui.clx(
275
+ "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2",
276
+ {
277
+ "[&_svg]:text-ui-fg-disabled": action.disabled
278
+ }
279
+ ),
280
+ children: [
281
+ action.icon,
282
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: action.label })
283
+ ]
284
+ },
285
+ index2
286
+ );
287
+ }
288
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
289
+ ui.DropdownMenu.Item,
290
+ {
291
+ className: ui.clx(
292
+ "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2",
293
+ {
294
+ "[&_svg]:text-ui-fg-disabled": action.disabled
295
+ }
296
+ ),
297
+ asChild: true,
298
+ disabled: action.disabled,
299
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Link, { to: action.to, onClick: (e) => e.stopPropagation(), children: [
300
+ action.icon,
301
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: action.label })
302
+ ] })
303
+ }
304
+ ) }, index2);
305
+ }),
306
+ !isLast && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {})
307
+ ] }, index);
308
+ }) })
309
+ ] });
310
+ };
311
+ async function loader({ params }) {
312
+ const { id } = params;
313
+ return sdk.client.fetch(`/admin/reviews/${id}`, {
314
+ query: { fields: "id,author_name" }
315
+ });
316
+ }
317
+ const handle = {
318
+ breadcrumb: ({ data }) => {
319
+ var _a, _b;
320
+ return ((_a = data == null ? void 0 : data.review) == null ? void 0 : _a.author_name) || ((_b = data == null ? void 0 : data.review) == null ? void 0 : _b.id) || "Review";
321
+ }
322
+ };
323
+ const STATUS_COLORS = {
324
+ pending: "blue",
325
+ approved: "green",
326
+ rejected: "red"
327
+ };
328
+ const STATUS_LABELS = {
329
+ pending: "Pending",
330
+ approved: "Approved",
331
+ rejected: "Rejected"
332
+ };
333
+ const ReviewDetailPage = () => {
334
+ const { id } = reactRouterDom.useParams();
335
+ const navigate = reactRouterDom.useNavigate();
336
+ const prompt = ui.usePrompt();
337
+ const { data, isLoading } = useReview(id);
338
+ const { mutate: updateReview, isPending: isUpdating } = useUpdateReview(id);
339
+ const { mutate: deleteReview } = useDeleteReview();
340
+ const review = data == null ? void 0 : data.review;
341
+ const handleApprove = () => {
342
+ updateReview(
343
+ { status: "approved" },
344
+ {
345
+ onSuccess: () => ui.toast.success("Review approved"),
346
+ onError: () => ui.toast.error("Failed to approve review")
347
+ }
348
+ );
349
+ };
350
+ const handleReject = () => {
351
+ updateReview(
352
+ { status: "rejected" },
353
+ {
354
+ onSuccess: () => ui.toast.success("Review rejected"),
355
+ onError: () => ui.toast.error("Failed to reject review")
356
+ }
357
+ );
358
+ };
359
+ const handleDelete = async () => {
360
+ const confirmed = await prompt({
361
+ title: "Delete review?",
362
+ description: "This action cannot be undone.",
363
+ confirmText: "Delete",
364
+ cancelText: "Cancel",
365
+ variant: "danger"
366
+ });
367
+ if (confirmed) {
368
+ deleteReview(review.id, {
369
+ onSuccess: () => {
370
+ ui.toast.success("Review deleted");
371
+ navigate("/reviews");
372
+ },
373
+ onError: () => ui.toast.error("Failed to delete review")
374
+ });
375
+ }
376
+ };
377
+ if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading…" }) });
378
+ if (!review) return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Review not found." }) });
379
+ const fields = [
380
+ ["Status", /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: STATUS_COLORS[review.status], children: STATUS_LABELS[review.status] })],
381
+ ["Rating", `${review.rating} / 5`],
382
+ ["Author", review.author_name],
383
+ ...review.author_email ? [["Email", review.author_email]] : [],
384
+ ...review.title ? [["Title", review.title]] : [],
385
+ ["Review", /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-pre-wrap", children: review.body })],
386
+ ...review.product_id ? [["Product ID", /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-xs", children: review.product_id })]] : [],
387
+ ...review.order_id ? [["Order ID", /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-xs", children: review.order_id })]] : [],
388
+ ...review.customer_id ? [["Customer ID", /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-xs", children: review.customer_id })]] : [],
389
+ ["Received", new Date(review.created_at).toLocaleString()]
390
+ ];
391
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-4", children: [
392
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
393
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
394
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Review" }),
395
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
396
+ review.status === "pending" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
397
+ /* @__PURE__ */ jsxRuntime.jsx(
398
+ ui.Button,
399
+ {
400
+ size: "small",
401
+ variant: "secondary",
402
+ onClick: handleReject,
403
+ disabled: isUpdating,
404
+ children: "Reject"
405
+ }
406
+ ),
407
+ /* @__PURE__ */ jsxRuntime.jsx(
408
+ ui.Button,
409
+ {
410
+ size: "small",
411
+ variant: "primary",
412
+ onClick: handleApprove,
413
+ disabled: isUpdating,
414
+ children: "Approve"
415
+ }
416
+ )
417
+ ] }),
418
+ /* @__PURE__ */ jsxRuntime.jsx(
419
+ ActionMenu,
420
+ {
421
+ groups: [
422
+ {
423
+ actions: [
424
+ { label: "Delete", icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}), onClick: handleDelete }
425
+ ]
426
+ }
427
+ ]
428
+ }
429
+ )
430
+ ] })
431
+ ] }),
432
+ fields.map(([label, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-[180px_1fr] items-start px-6 py-4", children: [
433
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: label }),
434
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", as: "div", children: value })
435
+ ] }, label))
436
+ ] }),
437
+ review.activity && review.activity.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
438
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Activity" }) }),
439
+ review.activity.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 px-6 py-4", children: [
440
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
441
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: "grey", children: entry.type }),
442
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: new Date(entry.created_at).toLocaleString() })
443
+ ] }),
444
+ entry.note && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: entry.note })
445
+ ] }, entry.id))
446
+ ] })
447
+ ] });
448
+ };
449
+ const widgetModule = { widgets: [] };
450
+ const routeModule = {
451
+ routes: [
452
+ {
453
+ Component: ReviewsPage,
454
+ path: "/reviews",
455
+ handle: handle$1
456
+ },
457
+ {
458
+ Component: ReviewDetailPage,
459
+ path: "/reviews/:id",
460
+ handle,
461
+ loader
462
+ }
463
+ ]
464
+ };
465
+ const menuItemModule = {
466
+ menuItems: [
467
+ {
468
+ label: config.label,
469
+ icon: config.icon,
470
+ path: "/reviews",
471
+ nested: void 0,
472
+ rank: 10,
473
+ translationNs: void 0
474
+ }
475
+ ]
476
+ };
477
+ const formModule = { customFields: {} };
478
+ const displayModule = {
479
+ displays: {}
480
+ };
481
+ const i18nModule = { resources: {} };
482
+ const plugin = {
483
+ widgetModule,
484
+ routeModule,
485
+ menuItemModule,
486
+ formModule,
487
+ displayModule,
488
+ i18nModule
489
+ };
490
+ module.exports = plugin;