UNPKG

@ozzaim/medusa-plugin-product-reviews

Version:

A product review plugin for Medusa v1 that allows customers to leave reviews and ratings for products, including optional admin UI.

82 lines (81 loc) 8.28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.config = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const medusa_react_1 = require("medusa-react"); const ui_1 = require("@medusajs/ui"); const icons_1 = require("@medusajs/icons"); const react_1 = require("react"); const PAGE_SIZE = 5; const ProductReviewsPage = () => { const [statusFilter, setStatusFilter] = (0, react_1.useState)("all"); const [minRatingFilter, setMinRatingFilter] = (0, react_1.useState)("all"); const [pageIndex, setPageIndex] = (0, react_1.useState)(0); const queryKey = [ "product-review", statusFilter, minRatingFilter, pageIndex, PAGE_SIZE, ]; const queryParams = new URLSearchParams(); if (statusFilter !== "all") { if (statusFilter === "approved") queryParams.set("status", "approved"); else if (statusFilter === "pending") queryParams.set("status", "pending"); else if (statusFilter === "reject") queryParams.set("status", "reject"); } queryParams.set("limit", PAGE_SIZE.toString()); queryParams.set("offset", (pageIndex * PAGE_SIZE).toString()); const { data, isLoading, refetch } = (0, medusa_react_1.useAdminCustomQuery)(`/product-review?${queryParams.toString()}`, queryKey, { keepPreviousData: true, }); const { mutate: updateReview } = (0, medusa_react_1.useAdminCustomPost)("/product-review", ["product-review"]); const filteredReviews = (data?.reviews || []).filter((review) => { if (minRatingFilter === "all") return true; return review.rating >= parseInt(minRatingFilter); }); const totalCount = data?.count ?? 0; const pageCount = Math.ceil(totalCount / PAGE_SIZE); const handleReviewAction = (id, action) => { updateReview({ id, action }, { onSuccess: () => { refetch(); ui_1.toast.success("Success", { description: `Review ${action}d successfully ID: ${id}`, duration: 3000, }); }, onError: (err) => { console.error(`Review ${action} failed:`, err); ui_1.toast.error("Error", { description: `Failed to ${action} review ID: ${id}: ${err.message}`, duration: 3000, }); }, }); }; return ((0, jsx_runtime_1.jsxs)("div", { className: "p-8 shadow-elevation-card-rest bg-ui-bg-base w-full rounded-lg overflow-hidden", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-xl font-bold mb-4", children: "Product Reviews" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-4 mb-6 items-end justify-end", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "block text-sm font-medium mb-1 w-29", children: "Status" }), (0, jsx_runtime_1.jsxs)(ui_1.Select, { value: statusFilter, onValueChange: (val) => { setStatusFilter(val); setPageIndex(0); }, children: [(0, jsx_runtime_1.jsx)(ui_1.Select.Trigger, { children: (0, jsx_runtime_1.jsx)(ui_1.Select.Value, {}) }), (0, jsx_runtime_1.jsxs)(ui_1.Select.Content, { children: [(0, jsx_runtime_1.jsx)(ui_1.Select.Item, { value: "all", children: "All" }), (0, jsx_runtime_1.jsx)(ui_1.Select.Item, { value: "approved", children: "Approved" }), (0, jsx_runtime_1.jsx)(ui_1.Select.Item, { value: "pending", children: "Pending" })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "block text-sm font-medium mb-1 w-29", children: "Minimum Rating" }), (0, jsx_runtime_1.jsxs)(ui_1.Select, { value: minRatingFilter, onValueChange: (val) => { setMinRatingFilter(val); setPageIndex(0); }, children: [(0, jsx_runtime_1.jsx)(ui_1.Select.Trigger, { children: (0, jsx_runtime_1.jsx)(ui_1.Select.Value, {}) }), (0, jsx_runtime_1.jsxs)(ui_1.Select.Content, { children: [(0, jsx_runtime_1.jsx)(ui_1.Select.Item, { value: "all", children: "All" }), [1, 2, 3, 4, 5].map((r) => ((0, jsx_runtime_1.jsx)(ui_1.Select.Item, { value: r.toString(), children: r }, r)))] })] })] })] }), (0, jsx_runtime_1.jsxs)(ui_1.Table, { children: [(0, jsx_runtime_1.jsx)(ui_1.Table.Header, { children: (0, jsx_runtime_1.jsxs)(ui_1.Table.Row, { children: [(0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Product" }), (0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Variant" }), (0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Rating" }), (0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Title" }), (0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Content" }), (0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Customer" }), (0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Status" }), (0, jsx_runtime_1.jsx)(ui_1.Table.HeaderCell, { children: "Actions" })] }) }), (0, jsx_runtime_1.jsx)(ui_1.Table.Body, { children: filteredReviews.length ? (filteredReviews.map((review) => { const isApproved = review.approved; const toggleAction = isApproved ? "reject" : "approve"; const actionLabel = isApproved ? "Reject" : "Approve"; const actionVariant = isApproved ? "primary" : "secondary"; return ((0, jsx_runtime_1.jsxs)(ui_1.Table.Row, { className: "hover:bg-gray-100 !h-20", children: [(0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { className: "truncate max-w-[300px]", children: (0, jsx_runtime_1.jsx)("a", { href: `/a/products/${review.product_id}`, className: "text-blue-600 hover:underline", children: review.product.title || "Unknown Product" }) }), (0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { children: review.variant.title || "Unknown Variant" }), (0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { children: review.rating }), (0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { className: "truncate max-w-[300px]", children: review.title }), (0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { className: "max-w-[300px]", children: (0, jsx_runtime_1.jsx)("div", { className: " whitespace-pre-wrap break-words", children: review.content }) }), (0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { children: review.customer_email || review.customer_name }), (0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { children: (0, jsx_runtime_1.jsx)(ui_1.Badge, { color: isApproved ? "green" : "blue", children: isApproved ? "Approved" : "Pending" }) }), (0, jsx_runtime_1.jsx)(ui_1.Table.Cell, { children: (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)(ui_1.Button, { className: "!w-18", variant: actionVariant, size: "small", onClick: () => handleReviewAction(review.id, toggleAction), children: actionLabel }), (0, jsx_runtime_1.jsx)(ui_1.Button, { className: "!w-18", variant: "danger", size: "small", onClick: () => handleReviewAction(review.id, "delete"), children: "Delete" })] }) })] }, review.id)); })) : ((0, jsx_runtime_1.jsx)(ui_1.Table.Row, { children: (0, jsx_runtime_1.jsx)("td", { colSpan: 8, className: "!h-96", children: (0, jsx_runtime_1.jsx)("div", { className: "flex justify-center items-center h-full", children: (0, jsx_runtime_1.jsx)(icons_1.Spinner, { className: "animate-spin" }) }) }) })) })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex justify-end mt-4", children: (0, jsx_runtime_1.jsx)(ui_1.Table.Pagination, { count: totalCount, pageSize: PAGE_SIZE, pageIndex: pageIndex, pageCount: pageCount, canPreviousPage: pageIndex > 0, canNextPage: pageIndex < pageCount - 1, previousPage: () => setPageIndex((prev) => Math.max(prev - 1, 0)), nextPage: () => setPageIndex((prev) => Math.min(prev + 1, pageCount - 1)) }) }), (0, jsx_runtime_1.jsx)(ui_1.Toaster, {})] })); }; exports.config = { link: { label: "Reviews", icon: icons_1.ChatBubbleLeftRightSolid, }, }; exports.default = ProductReviewsPage;