@burdenoff/microfe-store 2026.625.5 → 2026.626.1
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/StoreRoutes.js +3 -3
- package/dist/StoreRoutes.js.map +1 -1
- package/dist/components/AccessDenied.js +3 -3
- package/dist/components/AccessDenied.js.map +1 -1
- package/dist/components/InstallAppModal.js +175 -255
- package/dist/components/InstallAppModal.js.map +1 -1
- package/dist/components/cart/CartDrawer.js +10 -10
- package/dist/components/cart/CartDrawer.js.map +1 -1
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/errors/CombinedGraphQLErrors.js +1 -1
- package/dist/node_modules/@apollo/client/errors/CombinedGraphQLErrors.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/errors/utils.js +1 -1
- package/dist/node_modules/@apollo/client/errors/utils.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/link/core/ApolloLink.js +2 -2
- package/dist/node_modules/@apollo/client/link/core/ApolloLink.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/link/subscriptions/index.js +2 -2
- package/dist/node_modules/@apollo/client/link/subscriptions/index.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/link/utils/createOperation.js +1 -1
- package/dist/node_modules/@apollo/client/link/utils/createOperation.js.map +1 -0
- package/dist/node_modules/@apollo/client/utilities/caching/sizes.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/graphql/print.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/graphql/print.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/caches.js +2 -2
- package/dist/node_modules/@apollo/client/utilities/internal/caches.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/checkDocument.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/internal/checkDocument.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/getMainDefinition.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/internal/getMainDefinition.js.map +1 -0
- package/dist/node_modules/@apollo/client/utilities/internal/getMemoryInternals.js +9 -0
- package/dist/node_modules/@apollo/client/utilities/internal/getMemoryInternals.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/getOperationDefinition.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/internal/getOperationDefinition.js.map +1 -0
- package/dist/node_modules/@apollo/client/utilities/internal/getOperationName.js +8 -0
- package/dist/node_modules/@apollo/client/utilities/internal/getOperationName.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/globals/global.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/internal/globals/global.js.map +1 -0
- package/dist/node_modules/@apollo/client/utilities/internal/globals/maybe.js +10 -0
- package/dist/node_modules/@apollo/client/utilities/internal/globals/maybe.js.map +1 -0
- package/dist/node_modules/@apollo/client/utilities/internal/isNonNullObject.js +8 -0
- package/dist/node_modules/@apollo/client/utilities/internal/isNonNullObject.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/makeUniqueId.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/internal/makeUniqueId.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/memoize.js +2 -2
- package/dist/node_modules/@apollo/client/utilities/internal/memoize.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/internal/stringifyForDisplay.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/internal/stringifyForDisplay.js.map +1 -0
- package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/invariant/index.js +1 -1
- package/dist/node_modules/@apollo/client/utilities/invariant/index.js.map +1 -0
- package/dist/node_modules/@apollo/client/version.js +6 -0
- package/dist/node_modules/@apollo/client/version.js.map +1 -0
- package/dist/node_modules/{.bun/@wry_caches@1.0.1/node_modules/@wry → @wry}/caches/lib/weak.js +1 -1
- package/dist/node_modules/@wry/caches/lib/weak.js.map +1 -0
- package/dist/node_modules/{.bun/@wry_trie@0.5.0/node_modules/@wry → @wry}/trie/lib/index.js +1 -1
- package/dist/node_modules/@wry/trie/lib/index.js.map +1 -0
- package/dist/node_modules/{.bun/graphql-ws@6.0.8_796ebe57c2b0ffe0/node_modules/graphql-ws → graphql-ws}/dist/client.js +1 -1
- package/dist/node_modules/graphql-ws/dist/client.js.map +1 -0
- package/dist/node_modules/{.bun/graphql-ws@6.0.8_796ebe57c2b0ffe0/node_modules/graphql-ws → graphql-ws}/dist/common-CGW11Fyb.js +1 -1
- package/dist/node_modules/graphql-ws/dist/common-CGW11Fyb.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/NotificationFactories.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/NotificationFactories.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/Observable.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/Observable.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/Subscriber.js +2 -2
- package/dist/node_modules/rxjs/dist/esm5/internal/Subscriber.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/Subscription.js +2 -2
- package/dist/node_modules/rxjs/dist/esm5/internal/Subscription.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/config.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/config.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/observable/empty.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/observable/empty.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/scheduler/timeoutProvider.js +2 -2
- package/dist/node_modules/rxjs/dist/esm5/internal/scheduler/timeoutProvider.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/symbol/observable.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/symbol/observable.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/util/UnsubscriptionError.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/util/UnsubscriptionError.js.map +1 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/arrRemove.js +11 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/arrRemove.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/util/createErrorClass.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/util/createErrorClass.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/util/errorContext.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/util/errorContext.js.map +1 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/identity.js +8 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/identity.js.map +1 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/isFunction.js +8 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/isFunction.js.map +1 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/noop.js +6 -0
- package/dist/node_modules/rxjs/dist/esm5/internal/util/noop.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/util/pipe.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/util/pipe.js.map +1 -0
- package/dist/node_modules/{.bun/rxjs@7.8.2/node_modules/rxjs → rxjs}/dist/esm5/internal/util/reportUnhandledError.js +1 -1
- package/dist/node_modules/rxjs/dist/esm5/internal/util/reportUnhandledError.js.map +1 -0
- package/dist/node_modules/{.bun/tslib@2.8.1 → rxjs}/node_modules/tslib/tslib.es6.js +1 -1
- package/dist/node_modules/rxjs/node_modules/tslib/tslib.es6.js.map +1 -0
- package/dist/pages/AdminDashboardPage.js +118 -91
- package/dist/pages/AdminDashboardPage.js.map +1 -1
- package/dist/pages/AdminReviewModerationPage.js +67 -61
- package/dist/pages/AdminReviewModerationPage.js.map +1 -1
- package/dist/pages/AdminStoresPage.js +424 -371
- package/dist/pages/AdminStoresPage.js.map +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js +303 -286
- package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
- package/dist/pages/AppDetailPage.js +172 -135
- package/dist/pages/AppDetailPage.js.map +1 -1
- package/dist/pages/AppSettingsPage.js +83 -300
- package/dist/pages/AppSettingsPage.js.map +1 -1
- package/dist/pages/CartPage.js +71 -79
- package/dist/pages/CartPage.js.map +1 -1
- package/dist/pages/InstalledAppsPage.js +120 -337
- package/dist/pages/InstalledAppsPage.js.map +1 -1
- package/dist/pages/MarketplaceHomePage.js +196 -205
- package/dist/pages/MarketplaceHomePage.js.map +1 -1
- package/dist/pages/MyLicensesPage.js +70 -75
- package/dist/pages/MyLicensesPage.js.map +1 -1
- package/dist/pages/OrderConfirmationPage.js +70 -37
- package/dist/pages/OrderConfirmationPage.js.map +1 -1
- package/dist/pages/OrdersPage.js +60 -68
- package/dist/pages/OrdersPage.js.map +1 -1
- package/dist/pages/ProductDetailPage.js +794 -752
- package/dist/pages/ProductDetailPage.js.map +1 -1
- package/dist/pages/ProductReviewsPage.js +24 -19
- package/dist/pages/ProductReviewsPage.js.map +1 -1
- package/dist/pages/PublisherDashboardPage.js +134 -99
- package/dist/pages/PublisherDashboardPage.js.map +1 -1
- package/dist/pages/PublisherRevenuePage.js +65 -63
- package/dist/pages/PublisherRevenuePage.js.map +1 -1
- package/dist/pages/PublisherSubmissionsPage.js +120 -111
- package/dist/pages/PublisherSubmissionsPage.js.map +1 -1
- package/dist/pages/PublisherSubmitPage.js +197 -184
- package/dist/pages/PublisherSubmitPage.js.map +1 -1
- package/dist/providers/StoreProvider.js +3 -3
- package/package.json +2 -2
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/errors/CombinedGraphQLErrors.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/errors/utils.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/link/core/ApolloLink.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/link/subscriptions/index.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/link/utils/createOperation.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/caching/sizes.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/graphql/print.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/caches.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/checkDocument.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/getMainDefinition.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/getMemoryInternals.js +0 -9
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/getMemoryInternals.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/getOperationDefinition.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/getOperationName.js +0 -8
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/getOperationName.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/globals/global.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/globals/maybe.js +0 -10
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/globals/maybe.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/isNonNullObject.js +0 -8
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/isNonNullObject.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/makeUniqueId.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/memoize.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/internal/stringifyForDisplay.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/utilities/invariant/index.js.map +0 -1
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/version.js +0 -6
- package/dist/node_modules/.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo/client/version.js.map +0 -1
- package/dist/node_modules/.bun/@wry_caches@1.0.1/node_modules/@wry/caches/lib/weak.js.map +0 -1
- package/dist/node_modules/.bun/@wry_trie@0.5.0/node_modules/@wry/trie/lib/index.js.map +0 -1
- package/dist/node_modules/.bun/graphql-ws@6.0.8_796ebe57c2b0ffe0/node_modules/graphql-ws/dist/client.js.map +0 -1
- package/dist/node_modules/.bun/graphql-ws@6.0.8_796ebe57c2b0ffe0/node_modules/graphql-ws/dist/common-CGW11Fyb.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/NotificationFactories.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/Observable.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/Subscriber.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/Subscription.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/config.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/observable/empty.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/scheduler/timeoutProvider.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/symbol/observable.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/UnsubscriptionError.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/arrRemove.js +0 -11
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/arrRemove.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/createErrorClass.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/errorContext.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/identity.js +0 -8
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/identity.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/isFunction.js +0 -8
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/isFunction.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/noop.js +0 -6
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/noop.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/pipe.js.map +0 -1
- package/dist/node_modules/.bun/rxjs@7.8.2/node_modules/rxjs/dist/esm5/internal/util/reportUnhandledError.js.map +0 -1
- package/dist/node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.es6.js.map +0 -1
- /package/dist/node_modules/{.bun/@apollo_client@4.1.9_e571b0b00fd00a97/node_modules/@apollo → @apollo}/client/utilities/caching/sizes.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../src/pages/AdminDashboardPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport {\n LayoutDashboard,\n PackageCheck,\n Store as StoreIcon,\n MessageSquareWarning,\n Users,\n Check,\n X,\n ExternalLink,\n CheckCircle,\n XCircle,\n RefreshCw,\n ChevronDown,\n ChevronUp,\n} from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\nimport { formatPriceForModel } from '../utils';\n\ntype RecentProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature?: string | null;\n category?: string | null;\n icon?: string | null;\n price: number;\n currency: string;\n pricingModel: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n publisher?: { name: string; email: string } | null;\n};\n\ntype ActivityProduct = {\n id: string;\n slug: string;\n name: string;\n icon?: string | null;\n status: string;\n publishedAt?: string | null;\n reviewedAt?: string | null;\n reviewedBy?: string | null;\n rejectedReason?: string | null;\n};\n\ntype DashboardState = {\n pendingCount: number;\n totalStores: number;\n recentPending: RecentProduct[];\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n loading: boolean;\n error: string | null;\n};\n\nconst DASHBOARD_QUERY = /* GraphQL */ `\n query AdminDashboardData {\n recentPending: pendingReviewProducts(limit: 5) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n category\n icon\n price\n currency\n pricingModel\n description\n submittedAt\n submittedBy\n publisher {\n name\n email\n }\n }\n }\n stores {\n id\n }\n recentPublished: products(filter: { status: PUBLISHED }, limit: 5) {\n id\n slug\n name\n icon\n status\n publishedAt\n reviewedBy\n }\n recentRejected: products(filter: { status: REJECTED }, limit: 5) {\n id\n slug\n name\n icon\n status\n reviewedAt\n rejectedReason\n }\n }\n`;\n\nconst APPROVE_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!) {\n approveProduct(id: $id) {\n id\n status\n }\n }\n`;\n\nconst REJECT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nexport const AdminDashboardPage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n\n const [state, setState] = useState<DashboardState>({\n pendingCount: 0,\n totalStores: 0,\n recentPending: [],\n recentPublished: [],\n recentRejected: [],\n loading: true,\n error: null,\n });\n const [actingId, setActingId] = useState<string | null>(null);\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [actionError, setActionError] = useState<string | null>(null);\n const [viewingId, setViewingId] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setState((prev) => ({ ...prev, loading: true, error: null }));\n try {\n const result = await graphqlFetch<{\n recentPending: { totalCount: number; products: RecentProduct[] };\n stores: Array<{ id: string }>;\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n }>({\n gateway: 'global',\n query: DASHBOARD_QUERY,\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load dashboard');\n }\n setState({\n pendingCount: result.data?.recentPending.totalCount ?? 0,\n totalStores: result.data?.stores.length ?? 0,\n recentPending: result.data?.recentPending.products ?? [],\n recentPublished: result.data?.recentPublished ?? [],\n recentRejected: result.data?.recentRejected ?? [],\n loading: false,\n error: null,\n });\n } catch (err) {\n setState((prev) => ({\n ...prev,\n loading: false,\n error: err instanceof Error ? err.message : 'Failed to load dashboard',\n }));\n }\n }, [authToken]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const approve = async (id: string) => {\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_MUTATION,\n variables: { id },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setActingId(null);\n }\n };\n\n const reject = async (id: string) => {\n if (!rejectReason.trim()) return;\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_MUTATION,\n variables: { id, reason: rejectReason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setActingId(null);\n }\n };\n\n const formatDate = (iso?: string | null) => {\n if (!iso) return '—';\n return new Date(iso).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const kpiTiles = [\n {\n label: 'Pending submissions',\n value: state.pendingCount,\n icon: PackageCheck,\n href: `${basePath}/submissions`,\n },\n {\n label: 'Stores in registry',\n value: state.totalStores,\n icon: StoreIcon,\n href: `${basePath}/stores`,\n },\n {\n label: 'Review queue',\n value: '—',\n icon: MessageSquareWarning,\n href: `${basePath}/reviews`,\n },\n {\n label: 'Publishers',\n value: '—',\n icon: Users,\n href: `${basePath}/dashboard`,\n },\n ];\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n <header className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <LayoutDashboard className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Store admin dashboard</h1>\n </div>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={state.loading}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover disabled:opacity-60\"\n >\n <RefreshCw className={`size-4 ${state.loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Manage store submissions, multi-store registry, and review moderation.\n </p>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl space-y-6 p-6\">\n {state.error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {state.error}\n </div>\n )}\n\n {/* KPI tiles */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {kpiTiles.map((tile) => {\n const Icon = tile.icon;\n return (\n <button\n key={tile.label}\n type=\"button\"\n onClick={() => navigate(tile.href)}\n className=\"rounded-lg border border-border-default bg-surface-raised p-4 text-left transition-colors hover:bg-surface-hover\"\n >\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tile.label}\n </span>\n <Icon className=\"size-4 text-text-tertiary\" />\n </div>\n <div className=\"mt-2 text-3xl font-semibold text-text-primary\">\n {state.loading ? '…' : tile.value}\n </div>\n </button>\n );\n })}\n </div>\n\n {/* Recent Submissions */}\n <section>\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Recent Submissions</h2>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/submissions`)}\n className=\"inline-flex items-center gap-1 text-xs text-text-accent hover:underline\"\n >\n View all\n <ExternalLink className=\"size-3\" />\n </button>\n </div>\n\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-3 rounded-md border border-border-danger bg-surface-danger-muted px-4 py-2 text-sm text-text-danger\"\n >\n {actionError}\n </div>\n )}\n\n {state.loading ? (\n <div className=\"rounded-lg border border-border-default bg-surface-raised py-10 text-center text-sm text-text-secondary\">\n Loading…\n </div>\n ) : state.recentPending.length === 0 ? (\n <div className=\"rounded-lg border border-border-default bg-surface-raised py-10 text-center text-sm text-text-secondary\">\n No pending submissions.\n </div>\n ) : (\n <ul className=\"space-y-2\">\n {state.recentPending.map((p) => (\n <li\n key={p.id}\n className=\"overflow-hidden rounded-lg border border-border-default bg-surface-raised\"\n >\n <div className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-10 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-10 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n {p.type.slice(0, 2)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{p.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {p.publisher?.name ?? 'Unknown publisher'} ·{' '}\n {formatPriceForModel(p.price, p.currency, p.pricingModel)} ·{' '}\n {formatDate(p.submittedAt)}\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-3 py-1.5 text-xs text-text-secondary hover:bg-surface-hover\"\n >\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n Details\n </button>\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md bg-surface-success px-3 py-1.5 text-xs font-medium text-text-on-success hover:bg-surface-success-hover disabled:opacity-60\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n setActionError(null);\n }}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-danger bg-surface-danger-muted px-3 py-1.5 text-xs font-medium text-text-danger hover:bg-surface-danger hover:text-text-on-danger disabled:opacity-60\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n </div>\n </div>\n\n {viewingId === p.id && (\n <div className=\"border-t border-border-default bg-surface-muted p-4\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-text-secondary\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.slug}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Type</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.type}</dd>\n </div>\n {p.nature && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Nature</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.nature}</dd>\n </div>\n )}\n {p.category && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Category</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.category}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-text-secondary\">Pricing</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-text-tertiary\">({p.pricingModel})</span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Publisher</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-text-tertiary\">{p.publisher.email}</span>\n )}\n </dd>\n </div>\n {p.submittedBy && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted by</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.submittedBy}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted at</dt>\n <dd className=\"mt-0.5 text-text-primary\">{formatDate(p.submittedAt)}</dd>\n </div>\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-text-secondary\">Description</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.description}</dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-border-default bg-surface-muted px-4 py-3\">\n <label\n htmlFor={`reject-reason-${p.id}`}\n className=\"block text-xs font-medium text-text-secondary\"\n >\n Rejection reason (required)\n </label>\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label=\"Rejection reason\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-2 text-sm text-text-primary focus:border-border-accent focus:outline-none\"\n placeholder=\"Explain why this submission is being rejected…\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(null);\n setRejectReason('');\n }}\n className=\"rounded-md border border-border-default bg-surface-default px-3 py-1 text-xs text-text-primary hover:bg-surface-hover\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void reject(p.id)}\n disabled={!rejectReason.trim() || actingId === p.id}\n className=\"rounded-md bg-surface-danger px-3 py-1 text-xs font-medium text-text-on-danger hover:bg-surface-danger-hover disabled:opacity-60\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </section>\n\n {/* Recent Activity */}\n <section>\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Recent Activity</h2>\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {/* Recently Approved */}\n <div className=\"rounded-lg border border-border-default bg-surface-raised\">\n <div className=\"flex items-center gap-2 border-b border-border-default px-4 py-3\">\n <CheckCircle className=\"size-4 text-text-success\" />\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n Recently Approved\n </h3>\n </div>\n {state.loading ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">Loading…</p>\n ) : state.recentPublished.length === 0 ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n No approved products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-default\">\n {state.recentPublished.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n ?\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{p.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {p.reviewedBy ? `by ${p.reviewedBy} · ` : ''}\n {formatDate(p.publishedAt)}\n </p>\n </div>\n <span className=\"flex-shrink-0 rounded-full bg-surface-success-muted px-2 py-0.5 text-xs font-medium text-text-success\">\n Published\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n {/* Recently Rejected */}\n <div className=\"rounded-lg border border-border-default bg-surface-raised\">\n <div className=\"flex items-center gap-2 border-b border-border-default px-4 py-3\">\n <XCircle className=\"size-4 text-text-danger\" />\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n Recently Rejected\n </h3>\n </div>\n {state.loading ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">Loading…</p>\n ) : state.recentRejected.length === 0 ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n No rejected products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-default\">\n {state.recentRejected.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n ?\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{p.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {p.rejectedReason ? `\"${p.rejectedReason}\" · ` : ''}\n {formatDate(p.reviewedAt)}\n </p>\n </div>\n <span className=\"flex-shrink-0 rounded-full bg-surface-danger-muted px-2 py-0.5 text-xs font-medium text-text-danger\">\n Rejected\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n </section>\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;AA6DA,IAAM,IAAgC,ozBAgDhC,IAAiC,gHASjC,IAAgC,iJASzB,UAA+B;CAC1C,IAAM,IAAW,GAAa,EACxB,EAAE,cAAW,gBAAa,GAAU,EAEpC,CAAC,GAAO,KAAY,EAAyB;EACjD,cAAc;EACd,aAAa;EACb,eAAe,EAAE;EACjB,iBAAiB,EAAE;EACnB,gBAAgB,EAAE;EAClB,SAAS;EACT,OAAO;EACR,CAAC,EACI,CAAC,GAAU,KAAe,EAAwB,KAAK,EACvD,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAc,KAAmB,EAAS,GAAG,EAC9C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAEzD,IAAO,EAAY,YAAY;AACnC,KAAU,OAAU;GAAE,GAAG;GAAM,SAAS;GAAM,OAAO;GAAM,EAAE;AAC7D,MAAI;GACF,IAAM,IAAS,MAAM,EAKlB;IACD,SAAS;IACT,OAAO;IACP,WAAW,KAAa,KAAA;IACzB,CAAC;AACF,OAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,2BAA2B;AAE1E,KAAS;IACP,cAAc,EAAO,MAAM,cAAc,cAAc;IACvD,aAAa,EAAO,MAAM,OAAO,UAAU;IAC3C,eAAe,EAAO,MAAM,cAAc,YAAY,EAAE;IACxD,iBAAiB,EAAO,MAAM,mBAAmB,EAAE;IACnD,gBAAgB,EAAO,MAAM,kBAAkB,EAAE;IACjD,SAAS;IACT,OAAO;IACR,CAAC;WACK,GAAK;AACZ,MAAU,OAAU;IAClB,GAAG;IACH,SAAS;IACT,OAAO,aAAe,QAAQ,EAAI,UAAU;IAC7C,EAAE;;IAEJ,CAAC,EAAU,CAAC;AAEf,SAAgB;AACT,KAAM;IACV,CAAC,EAAK,CAAC;CAEV,IAAM,IAAU,OAAO,MAAe;AAEpC,EADA,EAAY,EAAG,EACf,EAAe,KAAK;AACpB,MAAI;GACF,IAAM,IAAS,MAAM,EAAa;IAChC,SAAS;IACT,OAAO;IACP,WAAW,EAAE,OAAI;IACjB,WAAW,KAAa,KAAA;IACzB,CAAC;AACF,OAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,kBAAkB;AAEjE,SAAM,GAAM;WACL,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,kBAAkB;YAC9D;AACR,KAAY,KAAK;;IAIf,IAAS,OAAO,MAAe;AAC9B,QAAa,MAAM,EAExB;GADA,EAAY,EAAG,EACf,EAAe,KAAK;AACpB,OAAI;IACF,IAAM,IAAS,MAAM,EAAa;KAChC,SAAS;KACT,OAAO;KACP,WAAW;MAAE;MAAI,QAAQ,EAAa,MAAM;MAAE;KAC9C,WAAW,KAAa,KAAA;KACzB,CAAC;AACF,QAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,mBAAmB;AAIlE,IAFA,EAAe,KAAK,EACpB,EAAgB,GAAG,EACnB,MAAM,GAAM;YACL,GAAK;AACZ,MAAe,aAAe,QAAQ,EAAI,UAAU,mBAAmB;aAC/D;AACR,MAAY,KAAK;;;IAIf,KAAc,MACb,IACE,IAAI,KAAK,EAAI,CAAC,mBAAmB,KAAA,GAAW;EACjD,OAAO;EACP,KAAK;EACL,MAAM;EACP,CAAC,GALe,KAQb,IAAW;EACf;GACE,OAAO;GACP,OAAO,EAAM;GACb,MAAM;GACN,MAAM,GAAG,EAAS;GACnB;EACD;GACE,OAAO;GACP,OAAO,EAAM;GACb,MAAM;GACN,MAAM,GAAG,EAAS;GACnB;EACD;GACE,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM,GAAG,EAAS;GACnB;EACD;GACE,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM,GAAG,EAAS;GACnB;EACF;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GAAQ,WAAU;aAChB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAiB,WAAU,4BAA6B,CAAA,EACxD,kBAAC,MAAD;OAAI,WAAU;iBAA0C;OAA0B,CAAA,CAC9E;SACN,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,KAAK,GAAM;MAC1B,UAAU,EAAM;MAChB,WAAU;gBAJZ,CAME,kBAAC,GAAD,EAAW,WAAW,UAAU,EAAM,UAAU,iBAAiB,MAAQ,CAAA,EAAA,UAElE;QACL;QACN,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA,CACA;;GACC,CAAA,EAET,kBAAC,QAAD;GAAM,WAAU;aAAhB;IACG,EAAM,SACL,kBAAC,OAAD;KACE,MAAK;KACL,WAAU;eAET,EAAM;KACH,CAAA;IAIR,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAS,KAAK,MAAS;MACtB,IAAM,IAAO,EAAK;AAClB,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAS,EAAK,KAAK;OAClC,WAAU;iBAJZ,CAME,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAK;SACD,CAAA,EACP,kBAAC,GAAD,EAAM,WAAU,6BAA8B,CAAA,CAC1C;WACN,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAM,UAAU,MAAM,EAAK;QACxB,CAAA,CACC;SAdF,EAAK,MAcH;OAEX;KACE,CAAA;IAGN,kBAAC,WAAD,EAAA,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA0C;OAAuB,CAAA,EAC/E,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAS,GAAG,EAAS,cAAc;OAClD,WAAU;iBAHZ,CAIC,YAEC,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,CAC5B;SACL;;KAEL,KACC,kBAAC,OAAD;MACE,MAAK;MACL,WAAU;gBAET;MACG,CAAA;KAGP,EAAM,UACL,kBAAC,OAAD;MAAK,WAAU;gBAA0G;MAEnH,CAAA,GACJ,EAAM,cAAc,WAAW,IACjC,kBAAC,OAAD;MAAK,WAAU;gBAA0G;MAEnH,CAAA,GAEN,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAM,cAAc,KAAK,MACxB,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACG,EAAE,OACD,kBAAC,OAAD;WACE,KAAK,EAAE;WACP,KAAI;WACJ,WAAU;WACV,CAAA,GAEF,kBAAC,OAAD;WAAK,WAAU;qBACZ,EAAE,KAAK,MAAM,GAAG,EAAE;WACf,CAAA;UAER,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBAAkD,EAAE;YAAS,CAAA,EAC1E,kBAAC,KAAD;YAAG,WAAU;sBAAb;aACG,EAAE,WAAW,QAAQ;aAAoB;aAAG;aAC5C,EAAoB,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa;aAAC;aAAG;aAC5D,EAAW,EAAE,YAAY;aACxB;cACA;;UACN,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,EAAa,MAAc,EAAE,KAAK,OAAO,EAAE,GAAG;aAC7D,WAAU;uBAHZ,CAKG,MAAc,EAAE,KACf,kBAAC,GAAD,EAAW,WAAU,YAAa,CAAA,GAElC,kBAAC,GAAD,EAAa,WAAU,YAAa,CAAA,EACpC,UAEK;;YACT,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,KAAK,EAAQ,EAAE,GAAG;aACjC,UAAU,MAAa,EAAE;aACzB,WAAU;uBAJZ,CAME,kBAAC,GAAD,EAAO,WAAU,YAAa,CAAA,EAAA,UAEvB;;YACT,kBAAC,UAAD;aACE,MAAK;aACL,eAAe;AAGb,cAFA,EAAe,EAAE,GAAG,EACpB,EAAgB,GAAG,EACnB,EAAe,KAAK;;aAEtB,UAAU,MAAa,EAAE;aACzB,WAAU;uBARZ,CAUE,kBAAC,GAAD,EAAG,WAAU,YAAa,CAAA,EAAA,SAEnB;;YACL;;UACF;;QAEL,MAAc,EAAE,MACf,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,MAAD;UAAI,WAAU;oBAAd;WACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAS,CAAA,EACzD,kBAAC,MAAD;YAAI,WAAU;sBAAsC,EAAE;YAAU,CAAA,CAC5D,EAAA,CAAA;WACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAS,CAAA,EACzD,kBAAC,MAAD;YAAI,WAAU;sBAA4B,EAAE;YAAU,CAAA,CAClD,EAAA,CAAA;WACL,EAAE,UACD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAW,CAAA,EAC3D,kBAAC,MAAD;YAAI,WAAU;sBAA4B,EAAE;YAAY,CAAA,CACpD,EAAA,CAAA;WAEP,EAAE,YACD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAa,CAAA,EAC7D,kBAAC,MAAD;YAAI,WAAU;sBAA4B,EAAE;YAAc,CAAA,CACtD,EAAA,CAAA;WAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAY,CAAA,EAC5D,kBAAC,MAAD;YAAI,WAAU;sBAAd;aACG,EAAoB,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa;aAAE;aAC3D,kBAAC,QAAD;cAAM,WAAU;wBAAhB;eAAqC;eAAE,EAAE;eAAa;eAAQ;;aAC3D;cACD,EAAA,CAAA;WACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAc,CAAA,EAC9D,kBAAC,MAAD;YAAI,WAAU;sBAAd,CACG,EAAE,WAAW,QAAQ,KACrB,EAAE,WAAW,SACZ,kBAAC,QAAD;aAAM,WAAU;uBAA4B,EAAE,UAAU;aAAa,CAAA,CAEpE;cACD,EAAA,CAAA;WACL,EAAE,eACD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAiB,CAAA,EACjE,kBAAC,MAAD;YAAI,WAAU;sBAAsC,EAAE;YAAiB,CAAA,CACnE,EAAA,CAAA;WAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAkC;YAAiB,CAAA,EACjE,kBAAC,MAAD;YAAI,WAAU;sBAA4B,EAAW,EAAE,YAAY;YAAM,CAAA,CACrE,EAAA,CAAA;WACL,EAAE,eACD,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAkC;aAAgB,CAAA,EAChE,kBAAC,MAAD;aAAI,WAAU;uBAA4B,EAAE;aAAiB,CAAA,CACzD;;WAEL;;SACD,CAAA;QAGP,MAAgB,EAAE,MACjB,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,SAAD;WACE,SAAS,iBAAiB,EAAE;WAC5B,WAAU;qBACX;WAEO,CAAA;UACR,kBAAC,YAAD;WACE,IAAI,iBAAiB,EAAE;WACvB,cAAW;WACX,OAAO;WACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;WAChD,MAAM;WACN,WAAU;WACV,aAAY;WACZ,CAAA;UACF,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,UAAD;YACE,MAAK;YACL,eAAe;AAEb,aADA,EAAe,KAAK,EACpB,EAAgB,GAAG;;YAErB,WAAU;sBACX;YAEQ,CAAA,EACT,kBAAC,UAAD;YACE,MAAK;YACL,eAAe,KAAK,EAAO,EAAE,GAAG;YAChC,UAAU,CAAC,EAAa,MAAM,IAAI,MAAa,EAAE;YACjD,WAAU;sBACX;YAEQ,CAAA,CACL;;UACF;;QAEL;SA/JE,EAAE,GA+JJ,CACL;MACC,CAAA;KAEC,EAAA,CAAA;IAGV,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAA+C;KAAoB,CAAA,EACjF,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,4BAA6B,CAAA,EACpD,kBAAC,MAAD;QAAI,WAAU;kBAAoE;QAE7E,CAAA,CACD;UACL,EAAM,UACL,kBAAC,KAAD;OAAG,WAAU;iBAAoD;OAAY,CAAA,GAC3E,EAAM,gBAAgB,WAAW,IACnC,kBAAC,KAAD;OAAG,WAAU;iBAAoD;OAE7D,CAAA,GAEJ,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAM,gBAAgB,KAAK,MAC1B,kBAAC,MAAD;QAAe,WAAU;kBAAzB;SACG,EAAE,OACD,kBAAC,OAAD;UACE,KAAK,EAAE;UACP,KAAI;UACJ,WAAU;UACV,CAAA,GAEF,kBAAC,OAAD;UAAK,WAAU;oBAAgH;UAEzH,CAAA;SAER,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAkD,EAAE;WAAS,CAAA,EAC1E,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAE,aAAa,MAAM,EAAE,WAAW,OAAO,IACzC,EAAW,EAAE,YAAY,CACxB;aACA;;SACN,kBAAC,QAAD;UAAM,WAAU;oBAAwG;UAEjH,CAAA;SACJ;UAtBI,EAAE,GAsBN,CACL;OACC,CAAA,CAEH;SAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,EAC/C,kBAAC,MAAD;QAAI,WAAU;kBAAoE;QAE7E,CAAA,CACD;UACL,EAAM,UACL,kBAAC,KAAD;OAAG,WAAU;iBAAoD;OAAY,CAAA,GAC3E,EAAM,eAAe,WAAW,IAClC,kBAAC,KAAD;OAAG,WAAU;iBAAoD;OAE7D,CAAA,GAEJ,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAM,eAAe,KAAK,MACzB,kBAAC,MAAD;QAAe,WAAU;kBAAzB;SACG,EAAE,OACD,kBAAC,OAAD;UACE,KAAK,EAAE;UACP,KAAI;UACJ,WAAU;UACV,CAAA,GAEF,kBAAC,OAAD;UAAK,WAAU;oBAAgH;UAEzH,CAAA;SAER,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAkD,EAAE;WAAS,CAAA,EAC1E,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAE,iBAAiB,IAAI,EAAE,eAAe,QAAQ,IAChD,EAAW,EAAE,WAAW,CACvB;aACA;;SACN,kBAAC,QAAD;UAAM,WAAU;oBAAsG;UAE/G,CAAA;SACJ;UAtBI,EAAE,GAsBN,CACL;OACC,CAAA,CAEH;QACF;OACE,EAAA,CAAA;IACL;KACH"}
|
|
1
|
+
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../src/pages/AdminDashboardPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport {\n LayoutDashboard,\n PackageCheck,\n Store as StoreIcon,\n MessageSquareWarning,\n Users,\n Check,\n X,\n ExternalLink,\n CheckCircle,\n XCircle,\n RefreshCw,\n ChevronDown,\n ChevronUp,\n} from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n GlassCard,\n EmphasisPanel,\n PagePurpose,\n NextSteps,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\nimport { formatPriceForModel } from '../utils';\n\ntype RecentProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature?: string | null;\n category?: string | null;\n icon?: string | null;\n price: number;\n currency: string;\n pricingModel: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n publisher?: { name: string; email: string } | null;\n};\n\ntype ActivityProduct = {\n id: string;\n slug: string;\n name: string;\n icon?: string | null;\n status: string;\n publishedAt?: string | null;\n reviewedAt?: string | null;\n reviewedBy?: string | null;\n rejectedReason?: string | null;\n};\n\ntype DashboardState = {\n pendingCount: number;\n totalStores: number;\n recentPending: RecentProduct[];\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n loading: boolean;\n error: string | null;\n};\n\nconst DASHBOARD_QUERY = /* GraphQL */ `\n query AdminDashboardData {\n recentPending: pendingReviewProducts(limit: 5) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n category\n icon\n price\n currency\n pricingModel\n description\n submittedAt\n submittedBy\n publisher {\n name\n email\n }\n }\n }\n stores {\n id\n }\n recentPublished: products(filter: { status: PUBLISHED }, limit: 5) {\n id\n slug\n name\n icon\n status\n publishedAt\n reviewedBy\n }\n recentRejected: products(filter: { status: REJECTED }, limit: 5) {\n id\n slug\n name\n icon\n status\n reviewedAt\n rejectedReason\n }\n }\n`;\n\nconst APPROVE_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!) {\n approveProduct(id: $id) {\n id\n status\n }\n }\n`;\n\nconst REJECT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nexport const AdminDashboardPage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n\n const [state, setState] = useState<DashboardState>({\n pendingCount: 0,\n totalStores: 0,\n recentPending: [],\n recentPublished: [],\n recentRejected: [],\n loading: true,\n error: null,\n });\n const [actingId, setActingId] = useState<string | null>(null);\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [actionError, setActionError] = useState<string | null>(null);\n const [viewingId, setViewingId] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setState((prev) => ({ ...prev, loading: true, error: null }));\n try {\n const result = await graphqlFetch<{\n recentPending: { totalCount: number; products: RecentProduct[] };\n stores: Array<{ id: string }>;\n recentPublished: ActivityProduct[];\n recentRejected: ActivityProduct[];\n }>({\n gateway: 'global',\n query: DASHBOARD_QUERY,\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load dashboard');\n }\n setState({\n pendingCount: result.data?.recentPending.totalCount ?? 0,\n totalStores: result.data?.stores.length ?? 0,\n recentPending: result.data?.recentPending.products ?? [],\n recentPublished: result.data?.recentPublished ?? [],\n recentRejected: result.data?.recentRejected ?? [],\n loading: false,\n error: null,\n });\n } catch (err) {\n setState((prev) => ({\n ...prev,\n loading: false,\n error: err instanceof Error ? err.message : 'Failed to load dashboard',\n }));\n }\n }, [authToken]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const approve = async (id: string) => {\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_MUTATION,\n variables: { id },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setActingId(null);\n }\n };\n\n const reject = async (id: string) => {\n if (!rejectReason.trim()) return;\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_MUTATION,\n variables: { id, reason: rejectReason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setActingId(null);\n }\n };\n\n const formatDate = (iso?: string | null) => {\n if (!iso) return '—';\n return new Date(iso).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const kpiTiles = [\n {\n label: 'Pending submissions',\n value: state.pendingCount,\n icon: PackageCheck,\n href: `${basePath}/submissions`,\n },\n {\n label: 'Stores in registry',\n value: state.totalStores,\n icon: StoreIcon,\n href: `${basePath}/stores`,\n },\n {\n label: 'Review queue',\n value: '—',\n icon: MessageSquareWarning,\n href: `${basePath}/reviews`,\n },\n {\n label: 'Publishers',\n value: '—',\n icon: Users,\n href: `${basePath}/dashboard`,\n },\n ];\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n <header className=\"border-b border-border-seam bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <LayoutDashboard className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Store admin dashboard</h1>\n </div>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={state.loading}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover disabled:opacity-60\"\n >\n <RefreshCw className={`size-4 ${state.loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Manage store submissions, multi-store registry, and review moderation.\n </p>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl space-y-6 p-6\">\n <PagePurpose>\n Your control room for the marketplace. At a glance, see how many products are waiting for\n review, how many stores are live, and what was recently approved or rejected — then act on\n the most urgent items (pending submissions) without leaving this page. Use the tiles to\n jump into the full queue, store registry, or review moderation.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"store-admin-dashboard\"\n steps={[\n {\n id: 'clear-pending',\n label: 'Clear the pending queue',\n description: 'Approve or reject submissions right below, or open the full queue.',\n onClick: () => navigate(`${basePath}/submissions`),\n done: !state.loading && state.pendingCount === 0,\n },\n {\n id: 'check-stores',\n label: 'Review your stores',\n description: 'Make sure each store has the right visibility and approval policy.',\n onClick: () => navigate(`${basePath}/stores`),\n },\n {\n id: 'moderate-reviews',\n label: 'Moderate customer reviews',\n description: 'Remove reviews that break the content guidelines.',\n onClick: () => navigate(`${basePath}/reviews`),\n },\n ]}\n />\n\n {state.error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {state.error}\n </div>\n )}\n\n {/* KPI tiles */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {kpiTiles.map((tile) => {\n const Icon = tile.icon;\n return (\n <button\n key={tile.label}\n type=\"button\"\n onClick={() => navigate(tile.href)}\n className=\"rounded-card border border-border-seam bg-bg-surface bg-accent-wash p-4 text-left shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2\"\n >\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {tile.label}\n </span>\n <Icon className=\"size-4 text-text-tertiary\" />\n </div>\n <div className=\"mt-2 text-3xl font-semibold tabular-nums text-text-primary\">\n {state.loading ? '…' : tile.value}\n </div>\n </button>\n );\n })}\n </div>\n\n {/* Recent Submissions */}\n <EmphasisPanel>\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Recent Submissions</h2>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/submissions`)}\n className=\"inline-flex items-center gap-1 text-xs text-text-accent hover:underline\"\n >\n View all\n <ExternalLink className=\"size-3\" />\n </button>\n </div>\n\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-3 rounded-md border border-border-danger bg-surface-danger-muted px-4 py-2 text-sm text-text-danger\"\n >\n {actionError}\n </div>\n )}\n\n {state.loading ? (\n <div className=\"rounded-lg border border-border-default bg-surface-raised py-10 text-center text-sm text-text-secondary\">\n Loading…\n </div>\n ) : state.recentPending.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"All caught up\"\n description=\"There are no submissions waiting for review. New products from publishers will surface here.\"\n />\n ) : (\n <ul className=\"space-y-2\">\n {state.recentPending.map((p) => (\n <li\n key={p.id}\n className=\"overflow-hidden rounded-lg border border-border-seam bg-surface-raised transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-[var(--shadow-pop)]\"\n >\n <div className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-10 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-10 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n {p.type.slice(0, 2)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{p.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {p.publisher?.name ?? 'Unknown publisher'} ·{' '}\n {formatPriceForModel(p.price, p.currency, p.pricingModel)} ·{' '}\n {formatDate(p.submittedAt)}\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-3 py-1.5 text-xs text-text-secondary hover:bg-surface-hover\"\n >\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n Details\n </button>\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md bg-surface-success px-3 py-1.5 text-xs font-medium text-text-on-success hover:bg-surface-success-hover disabled:opacity-60\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n setActionError(null);\n }}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-danger bg-surface-danger-muted px-3 py-1.5 text-xs font-medium text-text-danger hover:bg-surface-danger hover:text-text-on-danger disabled:opacity-60\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n </div>\n </div>\n\n {viewingId === p.id && (\n <div className=\"border-t border-border-seam bg-surface-muted p-4\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-text-secondary\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.slug}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Type</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.type}</dd>\n </div>\n {p.nature && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Nature</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.nature}</dd>\n </div>\n )}\n {p.category && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Category</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.category}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-text-secondary\">Pricing</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-text-tertiary\">({p.pricingModel})</span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Publisher</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-text-tertiary\">{p.publisher.email}</span>\n )}\n </dd>\n </div>\n {p.submittedBy && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted by</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.submittedBy}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted at</dt>\n <dd className=\"mt-0.5 text-text-primary\">{formatDate(p.submittedAt)}</dd>\n </div>\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-text-secondary\">Description</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.description}</dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-border-seam bg-surface-muted px-4 py-3\">\n <label\n htmlFor={`reject-reason-${p.id}`}\n className=\"block text-xs font-medium text-text-secondary\"\n >\n Rejection reason (required)\n </label>\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label=\"Rejection reason\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-2 text-sm text-text-primary focus:border-border-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n placeholder=\"Explain why this submission is being rejected…\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(null);\n setRejectReason('');\n }}\n className=\"rounded-md border border-border-default bg-surface-default px-3 py-1 text-xs text-text-primary hover:bg-surface-hover\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void reject(p.id)}\n disabled={!rejectReason.trim() || actingId === p.id}\n className=\"rounded-md bg-surface-danger px-3 py-1 text-xs font-medium text-text-on-danger hover:bg-surface-danger-hover disabled:opacity-60\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </EmphasisPanel>\n\n {/* Recent Activity */}\n <section>\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Recent Activity</h2>\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {/* Recently Approved */}\n <GlassCard className=\"overflow-hidden p-0\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-4 py-3\">\n <CheckCircle className=\"size-4 text-text-success\" />\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n Recently Approved\n </h3>\n </div>\n {state.loading ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">Loading…</p>\n ) : state.recentPublished.length === 0 ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n No approved products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {state.recentPublished.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n ?\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{p.name}</p>\n <p className=\"text-xs text-text-secondary\">\n {p.reviewedBy ? `by ${p.reviewedBy} · ` : ''}\n {formatDate(p.publishedAt)}\n </p>\n </div>\n <span className=\"flex-shrink-0 rounded-full bg-surface-success-muted px-2 py-0.5 text-xs font-medium text-text-success\">\n Published\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n\n {/* Recently Rejected */}\n <GlassCard className=\"overflow-hidden p-0\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-4 py-3\">\n <XCircle className=\"size-4 text-text-danger\" />\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n Recently Rejected\n </h3>\n </div>\n {state.loading ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">Loading…</p>\n ) : state.recentRejected.length === 0 ? (\n <p className=\"px-4 py-6 text-center text-sm text-text-secondary\">\n No rejected products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {state.recentRejected.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-8 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n ?\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{p.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {p.rejectedReason ? `\"${p.rejectedReason}\" · ` : ''}\n {formatDate(p.reviewedAt)}\n </p>\n </div>\n <span className=\"flex-shrink-0 rounded-full bg-surface-danger-muted px-2 py-0.5 text-xs font-medium text-text-danger\">\n Rejected\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n </section>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -4,81 +4,82 @@ import { ArrowLeft as i, MessageSquareWarning as a, Search as o, X as s } from "
|
|
|
4
4
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
5
5
|
import { useNavigate as u } from "react-router-dom";
|
|
6
6
|
import { graphqlFetch as d } from "@burdenoff/fe-libs/shared/graphql";
|
|
7
|
+
import { GlassCard as f, IllustratedEmptyState as p, PagePurpose as m } from "@burdenoff/fe-libs/ui";
|
|
7
8
|
//#region src/pages/AdminReviewModerationPage.tsx
|
|
8
|
-
var
|
|
9
|
-
let
|
|
9
|
+
var h = "query SearchProductsForModeration($query: String!) { searchProducts(query: $query, limit: 10) { id name slug } }", g = "query ProductReviewsForModeration($productId: String!) { productReviews(productId: $productId) { id userId rating title comment status helpful createdAt } }", _ = "mutation RejectReview($id: ID!) { rejectReview(id: $id) { id status } }", v = () => {
|
|
10
|
+
let v = u(), { authToken: y } = e(), [b, x] = r(""), [S, C] = r([]), [w, T] = r(!1), [E, D] = r(!1), [O, k] = r(null), [A, j] = r([]), [M, N] = r(!1), [P, F] = r(null), [I, L] = r(null), R = n(null), z = n(null);
|
|
10
11
|
t(() => {
|
|
11
12
|
let e = (e) => {
|
|
12
|
-
|
|
13
|
+
z.current && !z.current.contains(e.target) && D(!1);
|
|
13
14
|
};
|
|
14
15
|
return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
15
16
|
}, []);
|
|
16
|
-
let
|
|
17
|
-
if (
|
|
18
|
-
|
|
17
|
+
let B = (e) => {
|
|
18
|
+
if (x(e), k(null), j([]), F(null), R.current && clearTimeout(R.current), !e.trim()) {
|
|
19
|
+
C([]), D(!1);
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
R.current = setTimeout(async () => {
|
|
23
|
+
T(!0);
|
|
23
24
|
try {
|
|
24
|
-
|
|
25
|
+
C((await d({
|
|
25
26
|
gateway: "global",
|
|
26
|
-
query:
|
|
27
|
+
query: h,
|
|
27
28
|
variables: { query: e },
|
|
28
|
-
authToken:
|
|
29
|
-
})).data?.searchProducts ?? []),
|
|
29
|
+
authToken: y || void 0
|
|
30
|
+
})).data?.searchProducts ?? []), D(!0);
|
|
30
31
|
} catch {
|
|
31
|
-
|
|
32
|
+
C([]);
|
|
32
33
|
} finally {
|
|
33
|
-
|
|
34
|
+
T(!1);
|
|
34
35
|
}
|
|
35
36
|
}, 300);
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
|
|
37
|
+
}, V = (e) => {
|
|
38
|
+
k(e), x(e.name), D(!1), H(e.id);
|
|
39
|
+
}, H = async (e) => {
|
|
40
|
+
N(!0), F(null);
|
|
40
41
|
try {
|
|
41
42
|
let t = await d({
|
|
42
43
|
gateway: "global",
|
|
43
|
-
query:
|
|
44
|
+
query: g,
|
|
44
45
|
variables: { productId: e },
|
|
45
|
-
authToken:
|
|
46
|
+
authToken: y || void 0
|
|
46
47
|
});
|
|
47
48
|
if (t.errors?.length) throw Error(t.errors[0]?.message ?? "Failed to fetch reviews");
|
|
48
|
-
|
|
49
|
+
j(t.data?.productReviews ?? []);
|
|
49
50
|
} catch (e) {
|
|
50
|
-
|
|
51
|
+
F(e instanceof Error ? e.message : "Failed to fetch reviews");
|
|
51
52
|
} finally {
|
|
52
|
-
|
|
53
|
+
N(!1);
|
|
53
54
|
}
|
|
54
|
-
},
|
|
55
|
-
|
|
55
|
+
}, U = async (e) => {
|
|
56
|
+
L(e), F(null);
|
|
56
57
|
try {
|
|
57
58
|
let t = await d({
|
|
58
59
|
gateway: "global",
|
|
59
|
-
query:
|
|
60
|
+
query: _,
|
|
60
61
|
variables: { id: e },
|
|
61
|
-
authToken:
|
|
62
|
+
authToken: y || void 0
|
|
62
63
|
});
|
|
63
64
|
if (t.errors?.length) throw Error(t.errors[0]?.message ?? "Failed to remove review");
|
|
64
|
-
|
|
65
|
+
j((t) => t.filter((t) => t.id !== e));
|
|
65
66
|
} catch (e) {
|
|
66
|
-
|
|
67
|
+
F(e instanceof Error ? e.message : "Failed to remove review");
|
|
67
68
|
} finally {
|
|
68
|
-
|
|
69
|
+
L(null);
|
|
69
70
|
}
|
|
70
71
|
};
|
|
71
72
|
return /* @__PURE__ */ l("div", {
|
|
72
73
|
className: "min-h-screen bg-surface-default",
|
|
73
74
|
children: [/* @__PURE__ */ c("header", {
|
|
74
|
-
className: "border-b border-border-
|
|
75
|
+
className: "border-b border-border-seam bg-surface-raised",
|
|
75
76
|
children: /* @__PURE__ */ l("div", {
|
|
76
77
|
className: "mx-auto max-w-7xl px-6 py-4",
|
|
77
78
|
children: [/* @__PURE__ */ l("div", {
|
|
78
79
|
className: "flex items-center gap-3",
|
|
79
80
|
children: [/* @__PURE__ */ l("button", {
|
|
80
81
|
type: "button",
|
|
81
|
-
onClick: () =>
|
|
82
|
+
onClick: () => v(-1),
|
|
82
83
|
"aria-label": "Go back",
|
|
83
84
|
className: "inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary",
|
|
84
85
|
children: [/* @__PURE__ */ c(i, { className: "size-4" }), "Back"]
|
|
@@ -99,10 +100,13 @@ var f = "query SearchProductsForModeration($query: String!) { searchProducts(que
|
|
|
99
100
|
children: /* @__PURE__ */ l("div", {
|
|
100
101
|
className: "space-y-4",
|
|
101
102
|
children: [
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
-
|
|
103
|
+
/* @__PURE__ */ c(m, { children: "Keep the marketplace trustworthy. Pick any product, read every review customers have left for it, and remove ones that break the content guidelines (spam, abuse, or off-topic posts). This is targeted clean-up — search the exact product you need to moderate rather than scrolling an endless global feed." }),
|
|
104
|
+
/* @__PURE__ */ l(f, {
|
|
105
|
+
treatment: "glass",
|
|
106
|
+
glow: !0,
|
|
107
|
+
className: "p-4",
|
|
104
108
|
children: [/* @__PURE__ */ l("div", {
|
|
105
|
-
ref:
|
|
109
|
+
ref: z,
|
|
106
110
|
className: "relative",
|
|
107
111
|
children: [
|
|
108
112
|
/* @__PURE__ */ c("label", {
|
|
@@ -117,24 +121,24 @@ var f = "query SearchProductsForModeration($query: String!) { searchProducts(que
|
|
|
117
121
|
/* @__PURE__ */ c("input", {
|
|
118
122
|
id: "admin-review-product-search",
|
|
119
123
|
"aria-label": "Search product by name",
|
|
120
|
-
value:
|
|
121
|
-
onChange: (e) =>
|
|
122
|
-
onFocus: () =>
|
|
124
|
+
value: b,
|
|
125
|
+
onChange: (e) => B(e.target.value),
|
|
126
|
+
onFocus: () => S.length > 0 && D(!0),
|
|
123
127
|
placeholder: "Type a product name to search...",
|
|
124
128
|
autoComplete: "off",
|
|
125
129
|
className: "w-full rounded-md border border-border-default bg-surface-default py-2 pl-9 pr-3 text-sm text-text-primary outline-none focus:border-border-accent"
|
|
126
130
|
}),
|
|
127
|
-
|
|
131
|
+
w && /* @__PURE__ */ c("span", {
|
|
128
132
|
className: "absolute right-3 text-xs text-text-secondary",
|
|
129
133
|
children: "Searching…"
|
|
130
134
|
})
|
|
131
135
|
]
|
|
132
136
|
}),
|
|
133
|
-
|
|
137
|
+
E && S.length > 0 && /* @__PURE__ */ c("ul", {
|
|
134
138
|
className: "absolute z-10 mt-1 w-full rounded-md border border-border-default bg-surface-raised shadow-lg",
|
|
135
|
-
children:
|
|
139
|
+
children: S.map((e) => /* @__PURE__ */ c("li", { children: /* @__PURE__ */ l("button", {
|
|
136
140
|
type: "button",
|
|
137
|
-
onClick: () =>
|
|
141
|
+
onClick: () => V(e),
|
|
138
142
|
className: "w-full px-4 py-2.5 text-left text-sm hover:bg-surface-hover",
|
|
139
143
|
children: [/* @__PURE__ */ c("span", {
|
|
140
144
|
className: "font-medium text-text-primary",
|
|
@@ -145,57 +149,59 @@ var f = "query SearchProductsForModeration($query: String!) { searchProducts(que
|
|
|
145
149
|
})]
|
|
146
150
|
}) }, e.id))
|
|
147
151
|
}),
|
|
148
|
-
|
|
152
|
+
E && !w && S.length === 0 && b.trim() && /* @__PURE__ */ l("div", {
|
|
149
153
|
className: "absolute z-10 mt-1 w-full rounded-md border border-border-default bg-surface-raised px-4 py-3 text-sm text-text-secondary shadow-lg",
|
|
150
154
|
children: [
|
|
151
155
|
"No products found for “",
|
|
152
|
-
|
|
156
|
+
b,
|
|
153
157
|
"”"
|
|
154
158
|
]
|
|
155
159
|
})
|
|
156
160
|
]
|
|
157
|
-
}),
|
|
161
|
+
}), O && /* @__PURE__ */ l("p", {
|
|
158
162
|
className: "mt-2 text-xs text-text-secondary",
|
|
159
163
|
children: [
|
|
160
164
|
"Moderating:",
|
|
161
165
|
" ",
|
|
162
166
|
/* @__PURE__ */ c("span", {
|
|
163
167
|
className: "font-medium text-text-primary",
|
|
164
|
-
children:
|
|
168
|
+
children: O.name
|
|
165
169
|
}),
|
|
166
170
|
/* @__PURE__ */ l("span", {
|
|
167
171
|
className: "ml-1 text-text-tertiary",
|
|
168
172
|
children: [
|
|
169
173
|
"(",
|
|
170
|
-
|
|
174
|
+
O.id,
|
|
171
175
|
")"
|
|
172
176
|
]
|
|
173
177
|
})
|
|
174
178
|
]
|
|
175
179
|
})]
|
|
176
180
|
}),
|
|
177
|
-
|
|
181
|
+
P && /* @__PURE__ */ c("div", {
|
|
178
182
|
role: "alert",
|
|
179
183
|
className: "rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger",
|
|
180
|
-
children:
|
|
184
|
+
children: P
|
|
181
185
|
}),
|
|
182
|
-
|
|
186
|
+
M && /* @__PURE__ */ c("div", {
|
|
183
187
|
className: "rounded-lg border border-border-default bg-surface-raised p-10 text-center",
|
|
184
188
|
children: /* @__PURE__ */ c("p", {
|
|
185
189
|
className: "text-sm text-text-secondary",
|
|
186
190
|
children: "Loading reviews…"
|
|
187
191
|
})
|
|
188
192
|
}),
|
|
189
|
-
!
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
!M && !O ? /* @__PURE__ */ c(p, {
|
|
194
|
+
illustration: "empty-search",
|
|
195
|
+
title: "Search a product to moderate",
|
|
196
|
+
description: "Use the search box above to find a product by name. Its reviews will load here so you can inspect and remove any that violate the guidelines."
|
|
197
|
+
}) : !M && O && A.length === 0 ? /* @__PURE__ */ c(p, {
|
|
198
|
+
illustration: "empty-notifications",
|
|
199
|
+
title: "No reviews to moderate",
|
|
200
|
+
description: "This product has no reviews right now. Nothing here needs your attention."
|
|
195
201
|
}) : /* @__PURE__ */ c("ul", {
|
|
196
202
|
className: "space-y-3",
|
|
197
|
-
children:
|
|
198
|
-
className: "rounded-lg border border-border-
|
|
203
|
+
children: A.map((e) => /* @__PURE__ */ c("li", {
|
|
204
|
+
className: "rounded-lg border border-border-seam bg-surface-raised p-4 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-[var(--shadow-pop)]",
|
|
199
205
|
children: /* @__PURE__ */ l("div", {
|
|
200
206
|
className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",
|
|
201
207
|
children: [/* @__PURE__ */ l("div", { children: [
|
|
@@ -228,8 +234,8 @@ var f = "query SearchProductsForModeration($query: String!) { searchProducts(que
|
|
|
228
234
|
className: "flex items-center gap-2",
|
|
229
235
|
children: /* @__PURE__ */ l("button", {
|
|
230
236
|
type: "button",
|
|
231
|
-
onClick: () =>
|
|
232
|
-
disabled:
|
|
237
|
+
onClick: () => U(e.id),
|
|
238
|
+
disabled: I === e.id,
|
|
233
239
|
className: "inline-flex items-center gap-1.5 rounded-md border border-border-danger px-3 py-1.5 text-sm font-medium text-text-danger hover:bg-surface-danger-muted disabled:cursor-not-allowed disabled:opacity-60",
|
|
234
240
|
children: [/* @__PURE__ */ c(s, { className: "size-4" }), " Remove"]
|
|
235
241
|
})
|
|
@@ -243,6 +249,6 @@ var f = "query SearchProductsForModeration($query: String!) { searchProducts(que
|
|
|
243
249
|
});
|
|
244
250
|
};
|
|
245
251
|
//#endregion
|
|
246
|
-
export {
|
|
252
|
+
export { v as AdminReviewModerationPage };
|
|
247
253
|
|
|
248
254
|
//# sourceMappingURL=AdminReviewModerationPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminReviewModerationPage.js","names":[],"sources":["../../src/pages/AdminReviewModerationPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useRef, useEffect } from 'react';\nimport { ArrowLeft, Search, X, MessageSquareWarning } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\n\ntype ModerationReview = {\n id: string;\n userId: string;\n rating: number;\n title?: string | null;\n comment?: string | null;\n status: string;\n helpful: number;\n createdAt: string;\n};\n\ntype ProductSearchResult = {\n id: string;\n name: string;\n slug: string;\n};\n\nconst SEARCH_PRODUCTS_QUERY = `query SearchProductsForModeration($query: String!) { searchProducts(query: $query, limit: 10) { id name slug } }`;\nconst PRODUCT_REVIEWS_QUERY = `query ProductReviewsForModeration($productId: String!) { productReviews(productId: $productId) { id userId rating title comment status helpful createdAt } }`;\nconst REJECT_REVIEW_MUTATION = `mutation RejectReview($id: ID!) { rejectReview(id: $id) { id status } }`;\n\nexport const AdminReviewModerationPage: FC = () => {\n const navigate = useNavigate();\n const { authToken } = useStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [searchResults, setSearchResults] = useState<ProductSearchResult[]>([]);\n const [searchLoading, setSearchLoading] = useState(false);\n const [showDropdown, setShowDropdown] = useState(false);\n const [selectedProduct, setSelectedProduct] = useState<ProductSearchResult | null>(null);\n const [reviews, setReviews] = useState<ModerationReview[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [actingId, setActingId] = useState<string | null>(null);\n const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handleClickOutside = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setShowDropdown(false);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n const handleSearchChange = (value: string) => {\n setSearchQuery(value);\n setSelectedProduct(null);\n setReviews([]);\n setError(null);\n\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n if (!value.trim()) {\n setSearchResults([]);\n setShowDropdown(false);\n return;\n }\n\n searchDebounceRef.current = setTimeout(async () => {\n setSearchLoading(true);\n try {\n const result = await graphqlFetch<{ searchProducts: ProductSearchResult[] }>({\n gateway: 'global',\n query: SEARCH_PRODUCTS_QUERY,\n variables: { query: value },\n authToken: authToken || undefined,\n });\n setSearchResults(result.data?.searchProducts ?? []);\n setShowDropdown(true);\n } catch {\n setSearchResults([]);\n } finally {\n setSearchLoading(false);\n }\n }, 300);\n };\n\n const selectProduct = (product: ProductSearchResult) => {\n setSelectedProduct(product);\n setSearchQuery(product.name);\n setShowDropdown(false);\n void loadReviews(product.id);\n };\n\n const loadReviews = async (productId: string) => {\n setLoading(true);\n setError(null);\n try {\n const result = await graphqlFetch<{ productReviews: ModerationReview[] }>({\n gateway: 'global',\n query: PRODUCT_REVIEWS_QUERY,\n variables: { productId },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to fetch reviews');\n }\n\n setReviews(result.data?.productReviews ?? []);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch reviews');\n } finally {\n setLoading(false);\n }\n };\n\n const removeReview = async (reviewId: string) => {\n setActingId(reviewId);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_REVIEW_MUTATION,\n variables: { id: reviewId },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to remove review');\n }\n\n setReviews((prev) => prev.filter((review) => review.id !== reviewId));\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to remove review');\n } finally {\n setActingId(null);\n }\n };\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n <header className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n <div className=\"flex items-center gap-2\">\n <MessageSquareWarning className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Review Moderation</h1>\n </div>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Review published content by product and remove anything that violates guidelines\n </p>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl p-6\">\n <div className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-default bg-surface-raised p-4\">\n <div ref={dropdownRef} className=\"relative\">\n <label\n htmlFor=\"admin-review-product-search\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n Search product by name\n </label>\n <div className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-3 size-4 text-text-secondary\" />\n <input\n id=\"admin-review-product-search\"\n aria-label=\"Search product by name\"\n value={searchQuery}\n onChange={(e) => handleSearchChange(e.target.value)}\n onFocus={() => searchResults.length > 0 && setShowDropdown(true)}\n placeholder=\"Type a product name to search...\"\n autoComplete=\"off\"\n className=\"w-full rounded-md border border-border-default bg-surface-default py-2 pl-9 pr-3 text-sm text-text-primary outline-none focus:border-border-accent\"\n />\n {searchLoading && (\n <span className=\"absolute right-3 text-xs text-text-secondary\">Searching…</span>\n )}\n </div>\n\n {showDropdown && searchResults.length > 0 && (\n <ul className=\"absolute z-10 mt-1 w-full rounded-md border border-border-default bg-surface-raised shadow-lg\">\n {searchResults.map((product) => (\n <li key={product.id}>\n <button\n type=\"button\"\n onClick={() => selectProduct(product)}\n className=\"w-full px-4 py-2.5 text-left text-sm hover:bg-surface-hover\"\n >\n <span className=\"font-medium text-text-primary\">{product.name}</span>\n <span className=\"ml-2 text-xs text-text-secondary\">{product.slug}</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n\n {showDropdown &&\n !searchLoading &&\n searchResults.length === 0 &&\n searchQuery.trim() && (\n <div className=\"absolute z-10 mt-1 w-full rounded-md border border-border-default bg-surface-raised px-4 py-3 text-sm text-text-secondary shadow-lg\">\n No products found for “{searchQuery}”\n </div>\n )}\n </div>\n\n {selectedProduct && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n Moderating:{' '}\n <span className=\"font-medium text-text-primary\">{selectedProduct.name}</span>\n <span className=\"ml-1 text-text-tertiary\">({selectedProduct.id})</span>\n </p>\n )}\n </div>\n\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {error}\n </div>\n )}\n\n {loading && (\n <div className=\"rounded-lg border border-border-default bg-surface-raised p-10 text-center\">\n <p className=\"text-sm text-text-secondary\">Loading reviews…</p>\n </div>\n )}\n\n {!loading && selectedProduct && reviews.length === 0 ? (\n <div className=\"rounded-lg border border-border-default bg-surface-raised p-10 text-center\">\n <MessageSquareWarning className=\"mx-auto mb-2 size-8 text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">No reviews found for this product.</p>\n </div>\n ) : (\n <ul className=\"space-y-3\">\n {reviews.map((review) => (\n <li\n key={review.id}\n className=\"rounded-lg border border-border-default bg-surface-raised p-4\"\n >\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {review.title || 'Untitled review'}\n </p>\n <span className=\"rounded-full bg-surface-muted px-2 py-0.5 text-xs text-text-secondary\">\n {review.status}\n </span>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {review.comment || 'No comment provided.'}\n </p>\n <p className=\"mt-2 text-xs text-text-secondary\">\n User {review.userId} • Rating {review.rating}/5 • Helpful {review.helpful}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => removeReview(review.id)}\n disabled={actingId === review.id}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-danger px-3 py-1.5 text-sm font-medium text-text-danger hover:bg-surface-danger-muted disabled:cursor-not-allowed disabled:opacity-60\"\n >\n <X className=\"size-4\" /> Remove\n </button>\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminReviewModerationPage.js","names":[],"sources":["../../src/pages/AdminReviewModerationPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useRef, useEffect } from 'react';\nimport { ArrowLeft, Search, X, MessageSquareWarning } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { GlassCard, PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\n\ntype ModerationReview = {\n id: string;\n userId: string;\n rating: number;\n title?: string | null;\n comment?: string | null;\n status: string;\n helpful: number;\n createdAt: string;\n};\n\ntype ProductSearchResult = {\n id: string;\n name: string;\n slug: string;\n};\n\nconst SEARCH_PRODUCTS_QUERY = `query SearchProductsForModeration($query: String!) { searchProducts(query: $query, limit: 10) { id name slug } }`;\nconst PRODUCT_REVIEWS_QUERY = `query ProductReviewsForModeration($productId: String!) { productReviews(productId: $productId) { id userId rating title comment status helpful createdAt } }`;\nconst REJECT_REVIEW_MUTATION = `mutation RejectReview($id: ID!) { rejectReview(id: $id) { id status } }`;\n\nexport const AdminReviewModerationPage: FC = () => {\n const navigate = useNavigate();\n const { authToken } = useStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [searchResults, setSearchResults] = useState<ProductSearchResult[]>([]);\n const [searchLoading, setSearchLoading] = useState(false);\n const [showDropdown, setShowDropdown] = useState(false);\n const [selectedProduct, setSelectedProduct] = useState<ProductSearchResult | null>(null);\n const [reviews, setReviews] = useState<ModerationReview[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [actingId, setActingId] = useState<string | null>(null);\n const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handleClickOutside = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setShowDropdown(false);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n const handleSearchChange = (value: string) => {\n setSearchQuery(value);\n setSelectedProduct(null);\n setReviews([]);\n setError(null);\n\n if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);\n if (!value.trim()) {\n setSearchResults([]);\n setShowDropdown(false);\n return;\n }\n\n searchDebounceRef.current = setTimeout(async () => {\n setSearchLoading(true);\n try {\n const result = await graphqlFetch<{ searchProducts: ProductSearchResult[] }>({\n gateway: 'global',\n query: SEARCH_PRODUCTS_QUERY,\n variables: { query: value },\n authToken: authToken || undefined,\n });\n setSearchResults(result.data?.searchProducts ?? []);\n setShowDropdown(true);\n } catch {\n setSearchResults([]);\n } finally {\n setSearchLoading(false);\n }\n }, 300);\n };\n\n const selectProduct = (product: ProductSearchResult) => {\n setSelectedProduct(product);\n setSearchQuery(product.name);\n setShowDropdown(false);\n void loadReviews(product.id);\n };\n\n const loadReviews = async (productId: string) => {\n setLoading(true);\n setError(null);\n try {\n const result = await graphqlFetch<{ productReviews: ModerationReview[] }>({\n gateway: 'global',\n query: PRODUCT_REVIEWS_QUERY,\n variables: { productId },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to fetch reviews');\n }\n\n setReviews(result.data?.productReviews ?? []);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch reviews');\n } finally {\n setLoading(false);\n }\n };\n\n const removeReview = async (reviewId: string) => {\n setActingId(reviewId);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_REVIEW_MUTATION,\n variables: { id: reviewId },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to remove review');\n }\n\n setReviews((prev) => prev.filter((review) => review.id !== reviewId));\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to remove review');\n } finally {\n setActingId(null);\n }\n };\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n <header className=\"border-b border-border-seam bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n <div className=\"flex items-center gap-2\">\n <MessageSquareWarning className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Review Moderation</h1>\n </div>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Review published content by product and remove anything that violates guidelines\n </p>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl p-6\">\n <div className=\"space-y-4\">\n <PagePurpose>\n Keep the marketplace trustworthy. Pick any product, read every review customers have left\n for it, and remove ones that break the content guidelines (spam, abuse, or off-topic\n posts). This is targeted clean-up — search the exact product you need to moderate rather\n than scrolling an endless global feed.\n </PagePurpose>\n\n <GlassCard treatment=\"glass\" glow className=\"p-4\">\n <div ref={dropdownRef} className=\"relative\">\n <label\n htmlFor=\"admin-review-product-search\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n Search product by name\n </label>\n <div className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-3 size-4 text-text-secondary\" />\n <input\n id=\"admin-review-product-search\"\n aria-label=\"Search product by name\"\n value={searchQuery}\n onChange={(e) => handleSearchChange(e.target.value)}\n onFocus={() => searchResults.length > 0 && setShowDropdown(true)}\n placeholder=\"Type a product name to search...\"\n autoComplete=\"off\"\n className=\"w-full rounded-md border border-border-default bg-surface-default py-2 pl-9 pr-3 text-sm text-text-primary outline-none focus:border-border-accent\"\n />\n {searchLoading && (\n <span className=\"absolute right-3 text-xs text-text-secondary\">Searching…</span>\n )}\n </div>\n\n {showDropdown && searchResults.length > 0 && (\n <ul className=\"absolute z-10 mt-1 w-full rounded-md border border-border-default bg-surface-raised shadow-lg\">\n {searchResults.map((product) => (\n <li key={product.id}>\n <button\n type=\"button\"\n onClick={() => selectProduct(product)}\n className=\"w-full px-4 py-2.5 text-left text-sm hover:bg-surface-hover\"\n >\n <span className=\"font-medium text-text-primary\">{product.name}</span>\n <span className=\"ml-2 text-xs text-text-secondary\">{product.slug}</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n\n {showDropdown &&\n !searchLoading &&\n searchResults.length === 0 &&\n searchQuery.trim() && (\n <div className=\"absolute z-10 mt-1 w-full rounded-md border border-border-default bg-surface-raised px-4 py-3 text-sm text-text-secondary shadow-lg\">\n No products found for “{searchQuery}”\n </div>\n )}\n </div>\n\n {selectedProduct && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n Moderating:{' '}\n <span className=\"font-medium text-text-primary\">{selectedProduct.name}</span>\n <span className=\"ml-1 text-text-tertiary\">({selectedProduct.id})</span>\n </p>\n )}\n </GlassCard>\n\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {error}\n </div>\n )}\n\n {loading && (\n <div className=\"rounded-lg border border-border-default bg-surface-raised p-10 text-center\">\n <p className=\"text-sm text-text-secondary\">Loading reviews…</p>\n </div>\n )}\n\n {!loading && !selectedProduct ? (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Search a product to moderate\"\n description=\"Use the search box above to find a product by name. Its reviews will load here so you can inspect and remove any that violate the guidelines.\"\n />\n ) : !loading && selectedProduct && reviews.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"No reviews to moderate\"\n description=\"This product has no reviews right now. Nothing here needs your attention.\"\n />\n ) : (\n <ul className=\"space-y-3\">\n {reviews.map((review) => (\n <li\n key={review.id}\n className=\"rounded-lg border border-border-seam bg-surface-raised p-4 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-[var(--shadow-pop)]\"\n >\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {review.title || 'Untitled review'}\n </p>\n <span className=\"rounded-full bg-surface-muted px-2 py-0.5 text-xs text-text-secondary\">\n {review.status}\n </span>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {review.comment || 'No comment provided.'}\n </p>\n <p className=\"mt-2 text-xs text-text-secondary\">\n User {review.userId} • Rating {review.rating}/5 • Helpful {review.helpful}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => removeReview(review.id)}\n disabled={actingId === review.id}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-danger px-3 py-1.5 text-sm font-medium text-text-danger hover:bg-surface-danger-muted disabled:cursor-not-allowed disabled:opacity-60\"\n >\n <X className=\"size-4\" /> Remove\n </button>\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|