@burdenoff/microfe-store 2026.716.1 → 2026.720.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/pages/AdminDashboardPage.js.map +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js +110 -70
- package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
- package/dist/pages/CartPage.js +6 -6
- package/dist/pages/CartPage.js.map +1 -1
- package/dist/pages/OrderConfirmationPage.js +137 -80
- package/dist/pages/OrderConfirmationPage.js.map +1 -1
- package/dist/pages/ProductDetailPage.js +571 -513
- package/dist/pages/ProductDetailPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -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 ArrowRight,\n Clock,\n TrendingUp,\n} from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { GlassCard, IllustratedEmptyState } 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 productId?: 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 type?: string;\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 productId\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 type\n status\n publishedAt\n reviewedBy\n }\n recentRejected: products(filter: { status: REJECTED }, limit: 5) {\n id\n slug\n name\n icon\n type\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\nconst TYPE_COLORS: Record<string, string> = {\n NODE: 'bg-[var(--color-status-info-bg-subtle)] text-[var(--color-status-info-text)]',\n WORKFLOW: 'bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)]',\n INTEGRATION: 'bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)]',\n VIBEMODULE: 'bg-[var(--color-surface-accent-muted)] text-[var(--color-text-accent)]',\n};\n\nfunction typeColor(type?: string | null) {\n if (!type) return 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n return TYPE_COLORS[type] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n}\n\nfunction 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\nfunction timeAgo(iso?: string | null) {\n if (!iso) return '—';\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\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 kpiTiles = [\n {\n label: 'Pending review',\n value: state.pendingCount,\n icon: PackageCheck,\n href: `${basePath}/submissions`,\n accent:\n state.pendingCount > 0\n ? 'border-[var(--color-status-warning-text)]/30 bg-[var(--color-status-warning-bg-subtle)]/20'\n : '',\n valueClass:\n state.pendingCount > 0\n ? 'text-[var(--color-status-warning-text)]'\n : 'text-[var(--color-text-primary)]',\n cta: 'Review queue',\n },\n {\n label: 'Stores in registry',\n value: state.totalStores,\n icon: StoreIcon,\n href: `${basePath}/stores`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Manage stores',\n },\n {\n label: 'Review moderation',\n value: '—',\n icon: MessageSquareWarning,\n href: `${basePath}/reviews`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Moderate reviews',\n },\n {\n label: 'Publishers',\n value: '—',\n icon: Users,\n href: `${basePath}/dashboard`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'View publishers',\n },\n ];\n\n return (\n <div className=\"min-h-screen bg-[var(--color-surface-default)]\">\n {/* Header */}\n <header className=\"sticky top-0 z-10 border-b border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mx-auto max-w-6xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2.5\">\n <LayoutDashboard className=\"size-5 text-[var(--color-text-primary)]\" />\n <div>\n <h1 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n Store Admin Dashboard\n </h1>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n Marketplace control room — submissions, registry, moderation\n </p>\n </div>\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-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${state.loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-6xl space-y-8 p-6\">\n {state.error && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-3 text-sm text-[var(--color-text-danger)]\"\n >\n {state.error}\n </div>\n )}\n\n {/* KPI tiles */}\n <section>\n <div className=\"grid grid-cols-2 gap-4 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={`group relative overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] p-5 text-left shadow-[var(--shadow-elevation-1)] transition-all hover:border-[var(--color-border-strong)] hover:shadow-[var(--shadow-elevation-2)] ${tile.accent}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <p className=\"text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide\">\n {tile.label}\n </p>\n <p className={`mt-2 text-3xl font-bold tabular-nums ${tile.valueClass}`}>\n {state.loading ? (\n <span className=\"inline-block w-8 animate-pulse rounded bg-[var(--color-surface-muted)] h-8\" />\n ) : (\n tile.value\n )}\n </p>\n </div>\n <div className=\"rounded-lg bg-[var(--color-surface-muted)] p-2\">\n <Icon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n </div>\n <div className=\"mt-3 flex items-center gap-1 text-xs font-medium text-[var(--color-text-accent)] group-hover:underline\">\n {tile.cta}\n <ArrowRight className=\"size-3\" />\n </div>\n </button>\n );\n })}\n </div>\n </section>\n\n {/* Pending submissions */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Pending Submissions\n </h2>\n {state.pendingCount > 0 && (\n <span className=\"rounded-full bg-[var(--color-status-warning-bg-subtle)] px-2 py-0.5 text-xs font-semibold text-[var(--color-status-warning-text)]\">\n {state.pendingCount} waiting\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/submissions`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n View all\n <ExternalLink className=\"size-3.5\" />\n </button>\n </div>\n\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-3 rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-2.5 text-sm text-[var(--color-text-danger)]\"\n >\n {actionError}\n </div>\n )}\n\n {state.loading ? (\n <div className=\"space-y-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-xl bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPending.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"All caught up\"\n description=\"No submissions awaiting review. New products will appear here as publishers send them in.\"\n />\n ) : (\n <div className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentPending.map((p) => (\n <li key={p.id} className=\"overflow-hidden\">\n <div className=\"flex items-center gap-4 px-5 py-4\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-10 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {p.type.slice(0, 2)}\n </div>\n )}\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <p className=\"font-medium text-[var(--color-text-primary)] truncate\">\n {p.name}\n </p>\n <span\n className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${typeColor(p.type)}`}\n >\n {p.type}\n </span>\n </div>\n <div className=\"mt-0.5 flex items-center gap-3 flex-wrap text-xs text-[var(--color-text-secondary)]\">\n <span>{p.publisher?.name ?? 'Unknown publisher'}</span>\n <span>·</span>\n <span>{formatPriceForModel(p.price, p.currency, p.pricingModel)}</span>\n {p.submittedAt && (\n <>\n <span>·</span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {timeAgo(p.submittedAt)}\n </span>\n </>\n )}\n </div>\n </div>\n\n <div className=\"shrink-0 flex 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-[var(--color-border-default)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\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-[var(--color-surface-success)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-success)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\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-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-danger)] hover:bg-[var(--color-surface-danger)] hover:text-[var(--color-text-on-danger)] disabled:opacity-50 transition-colors\"\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-[var(--color-border-seam)] bg-[var(--color-surface-muted)] px-5 py-4\">\n <dl className=\"grid grid-cols-2 gap-x-8 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)]\">\n {p.slug}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Type</dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.type} · {p.nature ?? '—'}\n </dd>\n </div>\n {p.category && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Category\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.category}\n </dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Pricing\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({p.pricingModel})\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Publisher\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-[var(--color-text-muted)]\">\n {p.publisher.email}\n </span>\n )}\n </dd>\n </div>\n {p.productId && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Devportal app\n </dt>\n <dd className=\"mt-0.5\">\n <a\n href={`/devportal/apps/${p.productId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 font-mono text-[var(--color-text-accent)] hover:underline\"\n >\n {p.productId.slice(0, 16)}…\n <ExternalLink className=\"size-3\" />\n </a>\n </dd>\n </div>\n )}\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Description\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.description}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-danger-muted)]/30 px-5 py-4\">\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-danger)]\">\n Provide a rejection reason (the publisher will see this)\n </p>\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=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus:border-[var(--color-border-accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-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-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\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-[var(--color-surface-danger)] px-3 py-1 text-xs font-semibold text-[var(--color-text-on-danger)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n </section>\n\n {/* Recent Activity */}\n <section>\n <div className=\"mb-4 flex items-center gap-2\">\n <TrendingUp className=\"size-4 text-[var(--color-text-secondary)]\" />\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Recent Activity\n </h2>\n </div>\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.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-success-bg-subtle)]\">\n <CheckCircle className=\"size-4 text-[var(--color-status-success-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Approved\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPublished.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No approved products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-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 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\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-[var(--color-text-primary)]\">\n {p.name}\n </p>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {p.reviewedBy ? `by ${p.reviewedBy} · ` : ''}\n {formatDate(p.publishedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-success-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-success-text)]\">\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.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-error-bg-subtle)]\">\n <XCircle className=\"size-4 text-[var(--color-status-error-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Rejected\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentRejected.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No rejected products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-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 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\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-[var(--color-text-primary)]\">\n {p.name}\n </p>\n {p.rejectedReason && (\n <p\n className=\"truncate text-xs text-[var(--color-text-muted)]\"\n title={p.rejectedReason}\n >\n \"{p.rejectedReason}\"\n </p>\n )}\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {formatDate(p.reviewedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-error-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-error-text)]\">\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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"}
|
|
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 ArrowRight,\n Clock,\n TrendingUp,\n} from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { GlassCard, IllustratedEmptyState } 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 productId?: 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 type?: string;\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 productId\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 type\n status\n publishedAt\n reviewedBy\n }\n recentRejected: products(filter: { status: REJECTED }, limit: 5) {\n id\n slug\n name\n icon\n type\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\nconst DELIST_MUTATION = /* GraphQL */ `\n mutation DelistProduct($id: ID!, $reason: String) {\n delistProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst TYPE_COLORS: Record<string, string> = {\n NODE: 'bg-[var(--color-status-info-bg-subtle)] text-[var(--color-status-info-text)]',\n WORKFLOW: 'bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)]',\n INTEGRATION: 'bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)]',\n VIBEMODULE: 'bg-[var(--color-surface-accent-muted)] text-[var(--color-text-accent)]',\n};\n\nfunction typeColor(type?: string | null) {\n if (!type) return 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n return TYPE_COLORS[type] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n}\n\nfunction 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\nfunction timeAgo(iso?: string | null) {\n if (!iso) return '—';\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\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 delist = async (id: string) => {\n setActingId(id);\n setActionError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: DELIST_MUTATION,\n variables: { id, reason: rejectReason.trim() || undefined },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Delist failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await load();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Delist failed');\n } finally {\n setActingId(null);\n }\n };\n\n const kpiTiles = [\n {\n label: 'Pending review',\n value: state.pendingCount,\n icon: PackageCheck,\n href: `${basePath}/submissions`,\n accent:\n state.pendingCount > 0\n ? 'border-[var(--color-status-warning-text)]/30 bg-[var(--color-status-warning-bg-subtle)]/20'\n : '',\n valueClass:\n state.pendingCount > 0\n ? 'text-[var(--color-status-warning-text)]'\n : 'text-[var(--color-text-primary)]',\n cta: 'Review queue',\n },\n {\n label: 'Stores in registry',\n value: state.totalStores,\n icon: StoreIcon,\n href: `${basePath}/stores`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Manage stores',\n },\n {\n label: 'Review moderation',\n value: '—',\n icon: MessageSquareWarning,\n href: `${basePath}/reviews`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'Moderate reviews',\n },\n {\n label: 'Publishers',\n value: '—',\n icon: Users,\n href: `${basePath}/dashboard`,\n accent: '',\n valueClass: 'text-[var(--color-text-primary)]',\n cta: 'View publishers',\n },\n ];\n\n return (\n <div className=\"min-h-screen bg-[var(--color-surface-default)]\">\n {/* Header */}\n <header className=\"sticky top-0 z-10 border-b border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mx-auto max-w-6xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2.5\">\n <LayoutDashboard className=\"size-5 text-[var(--color-text-primary)]\" />\n <div>\n <h1 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n Store Admin Dashboard\n </h1>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n Marketplace control room — submissions, registry, moderation\n </p>\n </div>\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-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${state.loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-6xl space-y-8 p-6\">\n {state.error && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-3 text-sm text-[var(--color-text-danger)]\"\n >\n {state.error}\n </div>\n )}\n\n {/* KPI tiles */}\n <section>\n <div className=\"grid grid-cols-2 gap-4 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={`group relative overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] p-5 text-left shadow-[var(--shadow-elevation-1)] transition-all hover:border-[var(--color-border-strong)] hover:shadow-[var(--shadow-elevation-2)] ${tile.accent}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <p className=\"text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide\">\n {tile.label}\n </p>\n <p className={`mt-2 text-3xl font-bold tabular-nums ${tile.valueClass}`}>\n {state.loading ? (\n <span className=\"inline-block w-8 animate-pulse rounded bg-[var(--color-surface-muted)] h-8\" />\n ) : (\n tile.value\n )}\n </p>\n </div>\n <div className=\"rounded-lg bg-[var(--color-surface-muted)] p-2\">\n <Icon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n </div>\n <div className=\"mt-3 flex items-center gap-1 text-xs font-medium text-[var(--color-text-accent)] group-hover:underline\">\n {tile.cta}\n <ArrowRight className=\"size-3\" />\n </div>\n </button>\n );\n })}\n </div>\n </section>\n\n {/* Pending submissions */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Pending Submissions\n </h2>\n {state.pendingCount > 0 && (\n <span className=\"rounded-full bg-[var(--color-status-warning-bg-subtle)] px-2 py-0.5 text-xs font-semibold text-[var(--color-status-warning-text)]\">\n {state.pendingCount} waiting\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/submissions`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n View all\n <ExternalLink className=\"size-3.5\" />\n </button>\n </div>\n\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-3 rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-2.5 text-sm text-[var(--color-text-danger)]\"\n >\n {actionError}\n </div>\n )}\n\n {state.loading ? (\n <div className=\"space-y-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-xl bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPending.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"All caught up\"\n description=\"No submissions awaiting review. New products will appear here as publishers send them in.\"\n />\n ) : (\n <div className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\">\n <ul className=\"divide-y divide-[var(--color-border-seam)]\">\n {state.recentPending.map((p) => (\n <li key={p.id} className=\"overflow-hidden\">\n <div className=\"flex items-center gap-4 px-5 py-4\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-10 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\n {p.type.slice(0, 2)}\n </div>\n )}\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <p className=\"font-medium text-[var(--color-text-primary)] truncate\">\n {p.name}\n </p>\n <span\n className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${typeColor(p.type)}`}\n >\n {p.type}\n </span>\n </div>\n <div className=\"mt-0.5 flex items-center gap-3 flex-wrap text-xs text-[var(--color-text-secondary)]\">\n <span>{p.publisher?.name ?? 'Unknown publisher'}</span>\n <span>·</span>\n <span>{formatPriceForModel(p.price, p.currency, p.pricingModel)}</span>\n {p.submittedAt && (\n <>\n <span>·</span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {timeAgo(p.submittedAt)}\n </span>\n </>\n )}\n </div>\n </div>\n\n <div className=\"shrink-0 flex 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-[var(--color-border-default)] px-2.5 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\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-[var(--color-surface-success)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-success)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\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-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-danger)] hover:bg-[var(--color-surface-danger)] hover:text-[var(--color-text-on-danger)] disabled:opacity-50 transition-colors\"\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-[var(--color-border-seam)] bg-[var(--color-surface-muted)] px-5 py-4\">\n <dl className=\"grid grid-cols-2 gap-x-8 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)]\">\n {p.slug}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Type</dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.type} · {p.nature ?? '—'}\n </dd>\n </div>\n {p.category && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Category\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.category}\n </dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Pricing\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({p.pricingModel})\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Publisher\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-[var(--color-text-muted)]\">\n {p.publisher.email}\n </span>\n )}\n </dd>\n </div>\n {p.productId && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Devportal app\n </dt>\n <dd className=\"mt-0.5\">\n <a\n href={`/devportal/apps/${p.productId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 font-mono text-[var(--color-text-accent)] hover:underline\"\n >\n {p.productId.slice(0, 16)}…\n <ExternalLink className=\"size-3\" />\n </a>\n </dd>\n </div>\n )}\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Description\n </dt>\n <dd className=\"mt-0.5 text-[var(--color-text-primary)]\">\n {p.description}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-danger-muted)]/30 px-5 py-4\">\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-danger)]\">\n Provide a rejection reason (the publisher will see this)\n </p>\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=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus:border-[var(--color-border-accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-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-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\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-[var(--color-surface-danger)] px-3 py-1 text-xs font-semibold text-[var(--color-text-on-danger)] hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n </section>\n\n {/* Recent Activity */}\n <section>\n <div className=\"mb-4 flex items-center gap-2\">\n <TrendingUp className=\"size-4 text-[var(--color-text-secondary)]\" />\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n Recent Activity\n </h2>\n </div>\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.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-success-bg-subtle)]\">\n <CheckCircle className=\"size-4 text-[var(--color-status-success-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Approved\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentPublished.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No approved products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-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 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\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-[var(--color-text-primary)]\">\n {p.name}\n </p>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {p.reviewedBy ? `by ${p.reviewedBy} · ` : ''}\n {formatDate(p.publishedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-success-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-success-text)]\">\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.5 border-b border-[var(--color-border-seam)] px-4 py-3\">\n <div className=\"flex size-7 items-center justify-center rounded-md bg-[var(--color-status-error-bg-subtle)]\">\n <XCircle className=\"size-4 text-[var(--color-status-error-text)]\" />\n </div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-primary)]\">\n Recently Rejected\n </h3>\n <span className=\"ml-auto text-xs text-[var(--color-text-muted)]\">Last 5</span>\n </div>\n {state.loading ? (\n <div className=\"space-y-3 p-4\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-10 animate-pulse rounded-lg bg-[var(--color-surface-muted)]\"\n />\n ))}\n </div>\n ) : state.recentRejected.length === 0 ? (\n <p className=\"px-4 py-8 text-center text-sm text-[var(--color-text-muted)]\">\n No rejected products yet.\n </p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-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 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-surface-muted)] text-xs font-bold text-[var(--color-text-muted)] uppercase\">\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-[var(--color-text-primary)]\">\n {p.name}\n </p>\n {p.rejectedReason && (\n <p\n className=\"truncate text-xs text-[var(--color-text-muted)]\"\n title={p.rejectedReason}\n >\n \"{p.rejectedReason}\"\n </p>\n )}\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {formatDate(p.reviewedAt)}\n </p>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-status-error-bg-subtle)] px-2.5 py-1 text-xs font-medium text-[var(--color-status-error-text)]\">\n Rejected\n </span>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n </section>\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;AAmEA,IAAM,IAAgC,+1BAmDhC,IAAiC,gHASjC,IAAgC,iJAkBhC,IAAsC;CAC1C,MAAM;CACN,UAAU;CACV,aAAa;CACb,YAAY;CACb;AAED,SAAS,EAAU,GAAsB;AAEvC,QADK,IACE,EAAY,MAAS,uEADV;;AAIpB,SAAS,EAAW,GAAqB;AAEvC,QADK,IACE,IAAI,KAAK,EAAI,CAAC,mBAAmB,KAAA,GAAW;EACjD,OAAO;EACP,KAAK;EACL,MAAM;EACP,CAAC,GALe;;AAQnB,SAAS,EAAQ,GAAqB;AACpC,KAAI,CAAC,EAAK,QAAO;CACjB,IAAM,IAAO,KAAK,KAAK,GAAG,IAAI,KAAK,EAAI,CAAC,SAAS,EAC3C,IAAO,KAAK,MAAM,IAAO,IAAM;AACrC,KAAI,IAAO,GAAI,QAAO,GAAG,EAAK;CAC9B,IAAM,IAAM,KAAK,MAAM,IAAO,GAAG;AAGjC,QAFI,IAAM,KAAW,GAAG,EAAI,SAErB,GADM,KAAK,MAAM,IAAM,GAAG,CAClB;;AAGjB,IAAa,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;;;IA2Bf,IAAW;EACf;GACE,OAAO;GACP,OAAO,EAAM;GACb,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QACE,EAAM,eAAe,IACjB,+FACA;GACN,YACE,EAAM,eAAe,IACjB,4CACA;GACN,KAAK;GACN;EACD;GACE,OAAO;GACP,OAAO,EAAM;GACb,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QAAQ;GACR,YAAY;GACZ,KAAK;GACN;EACD;GACE,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QAAQ;GACR,YAAY;GACZ,KAAK;GACN;EACD;GACE,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM,GAAG,EAAS;GAClB,QAAQ;GACR,YAAY;GACZ,KAAK;GACN;EACF;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,UAAD;GAAQ,WAAU;aAChB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAiB,WAAU,2CAA4C,CAAA,EACvE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAyD;OAElE,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAA6C;OAEtD,CAAA,CACA,EAAA,CAAA,CACF;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;;IACF,CAAA;GACC,CAAA,EAET,kBAAC,QAAD;GAAM,WAAU;aAAhB;IACG,EAAM,SACL,kBAAC,OAAD;KACE,MAAK;KACL,WAAU;eAET,EAAM;KACH,CAAA;IAIR,kBAAC,WAAD,EAAA,UACE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAS,KAAK,MAAS;MACtB,IAAM,IAAO,EAAK;AAClB,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAS,EAAK,KAAK;OAClC,WAAW,0QAA0Q,EAAK;iBAJ5R,CAME,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAK;SACJ,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAW,wCAAwC,EAAK;mBACxD,EAAM,UACL,kBAAC,QAAD,EAAM,WAAU,8EAA+E,CAAA,GAE/F,EAAK;SAEL,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAM,WAAU,6CAA8C,CAAA;SAC1D,CAAA,CACF;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,EAAK,KACN,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA,CAC7B;UACC;SA1BF,EAAK,MA0BH;OAEX;KACE,CAAA,EACE,CAAA;IAGV,kBAAC,WAAD,EAAA,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA2D;QAEpE,CAAA,EACJ,EAAM,eAAe,KACpB,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACG,EAAM,cAAa,WACf;UAEL;UACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAS,GAAG,EAAS,cAAc;OAClD,WAAU;iBAHZ,CAIC,YAEC,kBAAC,GAAD,EAAc,WAAU,YAAa,CAAA,CAC9B;SACL;;KAEL,KACC,kBAAC,OAAD;MACE,MAAK;MACL,WAAU;gBAET;MACG,CAAA;KAGP,EAAM,UACL,kBAAC,OAAD;MAAK,WAAU;gBACZ;OAAC;OAAG;OAAG;OAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAEE,WAAU,iEACV,EAFK,EAEL,CACF;MACE,CAAA,GACJ,EAAM,cAAc,WAAW,IACjC,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAM,cAAc,KAAK,MACxB,kBAAC,MAAD;QAAe,WAAU;kBAAzB;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACG,EAAE,OACD,kBAAC,OAAD;YACE,KAAK,EAAE;YACP,KAAI;YACJ,WAAU;YACV,CAAA,GAEF,kBAAC,OAAD;YAAK,WAAU;sBACZ,EAAE,KAAK,MAAM,GAAG,EAAE;YACf,CAAA;WAGR,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,KAAD;cAAG,WAAU;wBACV,EAAE;cACD,CAAA,EACJ,kBAAC,QAAD;cACE,WAAW,uFAAuF,EAAU,EAAE,KAAK;wBAElH,EAAE;cACE,CAAA,CACH;gBACN,kBAAC,OAAD;aAAK,WAAU;uBAAf;cACE,kBAAC,QAAD,EAAA,UAAO,EAAE,WAAW,QAAQ,qBAA2B,CAAA;cACvD,kBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA;cACd,kBAAC,QAAD,EAAA,UAAO,EAAoB,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAQ,CAAA;cACtE,EAAE,eACD,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA,EACd,kBAAC,QAAD;eAAM,WAAU;yBAAhB,CACE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,EAC3B,EAAQ,EAAE,YAAY,CAClB;iBACN,EAAA,CAAA;cAED;eACF;;WAEN,kBAAC,OAAD;YAAK,WAAU;sBAAf;aACE,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,EAAa,MAAc,EAAE,KAAK,OAAO,EAAE,GAAG;cAC7D,WAAU;wBAHZ,CAKG,MAAc,EAAE,KACf,kBAAC,GAAD,EAAW,WAAU,YAAa,CAAA,GAElC,kBAAC,GAAD,EAAa,WAAU,YAAa,CAAA,EACpC,UAEK;;aACT,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,KAAK,EAAQ,EAAE,GAAG;cACjC,UAAU,MAAa,EAAE;cACzB,WAAU;wBAJZ,CAME,kBAAC,GAAD,EAAO,WAAU,YAAa,CAAA,EAAA,UAEvB;;aACT,kBAAC,UAAD;cACE,MAAK;cACL,eAAe;AAGb,eAFA,EAAe,EAAE,GAAG,EACpB,EAAgB,GAAG,EACnB,EAAe,KAAK;;cAEtB,UAAU,MAAa,EAAE;cACzB,WAAU;wBARZ,CAUE,kBAAC,GAAD,EAAG,WAAU,YAAa,CAAA,EAAA,SAEnB;;aACL;;WACF;;SAEL,MAAc,EAAE,MACf,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,MAAD;WAAI,WAAU;qBAAd;YACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAAS,CAAA,EACxE,kBAAC,MAAD;aAAI,WAAU;uBACX,EAAE;aACA,CAAA,CACD,EAAA,CAAA;YACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAAS,CAAA,EACxE,kBAAC,MAAD;aAAI,WAAU;uBAAd;cACG,EAAE;cAAK;cAAI,EAAE,UAAU;cACrB;eACD,EAAA,CAAA;YACL,EAAE,YACD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBACX,EAAE;aACA,CAAA,CACD,EAAA,CAAA;YAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBAAd;cACG,EAAoB,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa;cAAE;cAC3D,kBAAC,QAAD;eAAM,WAAU;yBAAhB;gBAAiD;gBAC7C,EAAE;gBAAa;gBACZ;;cACJ;eACD,EAAA,CAAA;YACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBAAd,CACG,EAAE,WAAW,QAAQ,KACrB,EAAE,WAAW,SACZ,kBAAC,QAAD;cAAM,WAAU;wBACb,EAAE,UAAU;cACR,CAAA,CAEN;eACD,EAAA,CAAA;YACL,EAAE,aACD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;aAAI,WAAU;uBAAiD;aAE1D,CAAA,EACL,kBAAC,MAAD;aAAI,WAAU;uBACZ,kBAAC,KAAD;cACE,MAAM,mBAAmB,EAAE;cAC3B,QAAO;cACP,KAAI;cACJ,WAAU;wBAJZ;eAMG,EAAE,UAAU,MAAM,GAAG,GAAG;eAAC;eAC1B,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;eACjC;;aACD,CAAA,CACD,EAAA,CAAA;YAEP,EAAE,eACD,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,MAAD;cAAI,WAAU;wBAAiD;cAE1D,CAAA,EACL,kBAAC,MAAD;cAAI,WAAU;wBACX,EAAE;cACA,CAAA,CACD;;YAEL;;UACD,CAAA;SAGP,MAAgB,EAAE,MACjB,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,KAAD;YAAG,WAAU;sBAA6D;YAEtE,CAAA;WACJ,kBAAC,YAAD;YACE,IAAI,iBAAiB,EAAE;YACvB,cAAW;YACX,OAAO;YACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;YAChD,MAAM;YACN,WAAU;YACV,aAAY;YACZ,CAAA;WACF,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,UAAD;aACE,MAAK;aACL,eAAe;AAEb,cADA,EAAe,KAAK,EACpB,EAAgB,GAAG;;aAErB,WAAU;uBACX;aAEQ,CAAA,EACT,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,KAAK,EAAO,EAAE,GAAG;aAChC,UAAU,CAAC,EAAa,MAAM,IAAI,MAAa,EAAE;aACjD,WAAU;uBACX;aAEQ,CAAA,CACL;;WACF;;SAEL;UApMI,EAAE,GAoMN,CACL;OACC,CAAA;MACD,CAAA;KAEA,EAAA,CAAA;IAGV,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAY,WAAU,6CAA8C,CAAA,EACpE,kBAAC,MAAD;MAAI,WAAU;gBAA2D;MAEpE,CAAA,CACD;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAa,WAAU,kDAAmD,CAAA;SACtE,CAAA;QACN,kBAAC,MAAD;SAAI,WAAU;mBAAyD;SAElE,CAAA;QACL,kBAAC,QAAD;SAAM,WAAU;mBAAiD;SAAa,CAAA;QAC1E;UACL,EAAM,UACL,kBAAC,OAAD;OAAK,WAAU;iBACZ;QAAC;QAAG;QAAG;QAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAEE,WAAU,iEACV,EAFK,EAEL,CACF;OACE,CAAA,GACJ,EAAM,gBAAgB,WAAW,IACnC,kBAAC,KAAD;OAAG,WAAU;iBAA+D;OAExE,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;qBACX,EAAE,QAAQ,KAAK,MAAM,GAAG,EAAE;UACxB,CAAA;SAER,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAE;WACD,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAE,aAAa,MAAM,EAAE,WAAW,OAAO,IACzC,EAAW,EAAE,YAAY,CACxB;aACA;;SACN,kBAAC,QAAD;UAAM,WAAU;oBAA2I;UAEpJ,CAAA;SACJ;UAxBI,EAAE,GAwBN,CACL;OACC,CAAA,CAEG;SAGZ,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAS,WAAU,gDAAiD,CAAA;SAChE,CAAA;QACN,kBAAC,MAAD;SAAI,WAAU;mBAAyD;SAElE,CAAA;QACL,kBAAC,QAAD;SAAM,WAAU;mBAAiD;SAAa,CAAA;QAC1E;UACL,EAAM,UACL,kBAAC,OAAD;OAAK,WAAU;iBACZ;QAAC;QAAG;QAAG;QAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAEE,WAAU,iEACV,EAFK,EAEL,CACF;OACE,CAAA,GACJ,EAAM,eAAe,WAAW,IAClC,kBAAC,KAAD;OAAG,WAAU;iBAA+D;OAExE,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;qBACX,EAAE,QAAQ,KAAK,MAAM,GAAG,EAAE;UACxB,CAAA;SAER,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAE;YACD,CAAA;WACH,EAAE,kBACD,kBAAC,KAAD;YACE,WAAU;YACV,OAAO,EAAE;sBAFX;aAGC;aACG,EAAE;aAAe;aACjB;;WAEN,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAW,EAAE,WAAW;YACvB,CAAA;WACA;;SACN,kBAAC,QAAD;UAAM,WAAU;oBAAuI;UAEhJ,CAAA;SACJ;UA/BI,EAAE,GA+BN,CACL;OACC,CAAA,CAEG;QACR;OACE,EAAA,CAAA;IACL;KACH"}
|
|
@@ -8,7 +8,7 @@ import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
|
8
8
|
import { useNavigate as se } from "react-router-dom";
|
|
9
9
|
import { IllustratedEmptyState as y, PagePurpose as ce } from "@burdenoff/fe-libs/ui";
|
|
10
10
|
//#region src/pages/AdminSubmissionsQueuePage.tsx
|
|
11
|
-
var le = "\n query PendingReviewProducts($limit: Int, $offset: Int) {\n pendingReviewProducts(limit: $limit, offset: $offset) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n productId\n zipFileUrl\n manifest\n requiredPermissions\n publisher {\n id\n name\n email\n }\n }\n }\n }\n", ue = "\n query AdminProducts($limit: Int, $offset: Int, $status: ProductStatus) {\n products(filter: { status: $status }, limit: $limit, offset: $offset) {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n productId\n zipFileUrl\n manifest\n requiredPermissions\n publisher {\n id\n name\n email\n }\n }\n }\n", de = "\n mutation ApproveProduct($id: ID!, $notes: String) {\n approveProduct(id: $id, notes: $notes) {\n id\n status\n publishedAt\n }\n }\n", fe = "\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n rejectedReason\n }\n }\n", pe = "\n query AdminAppPackageUrl($id: ID!) {\n application(id: $id) {\n id\n versions(first: 1) {\n edges {\n node {\n id\n packageUrl\n }\n }\n }\n }\n }\n",
|
|
11
|
+
var le = "\n query PendingReviewProducts($limit: Int, $offset: Int) {\n pendingReviewProducts(limit: $limit, offset: $offset) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n productId\n zipFileUrl\n manifest\n requiredPermissions\n publisher {\n id\n name\n email\n }\n }\n }\n }\n", ue = "\n query AdminProducts($limit: Int, $offset: Int, $status: ProductStatus) {\n products(filter: { status: $status }, limit: $limit, offset: $offset) {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n productId\n zipFileUrl\n manifest\n requiredPermissions\n publisher {\n id\n name\n email\n }\n }\n }\n", de = "\n mutation ApproveProduct($id: ID!, $notes: String) {\n approveProduct(id: $id, notes: $notes) {\n id\n status\n publishedAt\n }\n }\n", fe = "\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n rejectedReason\n }\n }\n", pe = "\n mutation DelistProduct($id: ID!, $reason: String) {\n delistProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n", me = "\n query AdminAppPackageUrl($id: ID!) {\n application(id: $id) {\n id\n versions(first: 1) {\n edges {\n node {\n id\n packageUrl\n }\n }\n }\n }\n }\n", he = "\n mutation RefreshApplicationVersionPackageUrl($id: ID!) {\n refreshApplicationVersionPackageUrl(id: $id)\n }\n", ge = "\n query AdminPendingVersionApps($limit: Int, $offset: Int) {\n applicationsWithPendingVersions(limit: $limit, offset: $offset) {\n totalCount\n edges {\n node {\n id\n name\n slug\n iconUrl\n shortDescription\n description\n versions(first: 5) {\n edges {\n node {\n id\n version\n status\n releaseNotes\n packageUrl\n packageSize\n isLatest\n reviewNotes\n manifest\n createdAt\n }\n }\n }\n }\n }\n }\n }\n", _e = "\n mutation AdminApproveApplicationVersion($id: ID!, $notes: String) {\n approveApplicationVersion(id: $id, notes: $notes) {\n id\n status\n }\n }\n", ve = "\n mutation AdminRejectApplicationVersion($id: ID!, $reason: String!) {\n rejectApplicationVersion(id: $id, reason: $reason) {\n id\n status\n }\n }\n", ye = [
|
|
12
12
|
{
|
|
13
13
|
label: "Pending review",
|
|
14
14
|
value: "PENDING_REVIEW"
|
|
@@ -46,10 +46,10 @@ var le = "\n query PendingReviewProducts($limit: Int, $offset: Int) {\n pend
|
|
|
46
46
|
"DATASINK",
|
|
47
47
|
"CONSOLE"
|
|
48
48
|
]);
|
|
49
|
-
function
|
|
49
|
+
function be(e) {
|
|
50
50
|
return b[e] ?? "bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]";
|
|
51
51
|
}
|
|
52
|
-
function
|
|
52
|
+
function xe(e) {
|
|
53
53
|
return x[e] ?? "bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]";
|
|
54
54
|
}
|
|
55
55
|
function C(e) {
|
|
@@ -62,12 +62,12 @@ function C(e) {
|
|
|
62
62
|
}) : "—";
|
|
63
63
|
}
|
|
64
64
|
var w = () => {
|
|
65
|
-
let b = se(), { authToken: x, basePath: w } = e(), [T, E] = a([]), [D, O] = a(0), [k, A] = a(!1), [j, M] = a(null), [N, P] = a(null), [F,
|
|
65
|
+
let b = se(), { authToken: x, basePath: w } = e(), [T, E] = a([]), [D, O] = a(0), [k, A] = a(!1), [j, M] = a(null), [N, P] = a(null), [F, Se] = a("PENDING_REVIEW"), [Ce, I] = a(null), [L, R] = a(""), [z, B] = a(null), [V, H] = a(null), [U, we] = a([]), [W, Te] = a(0), [G, K] = a(null), [Ee, q] = a(null), [J, Y] = a(""), [De, X] = a(null), [Oe, ke] = a({}), [Z, Ae] = a(null), Q = r(async () => {
|
|
66
66
|
A(!0), M(null);
|
|
67
67
|
try {
|
|
68
68
|
let e = await g({
|
|
69
69
|
gateway: "global",
|
|
70
|
-
query:
|
|
70
|
+
query: ge,
|
|
71
71
|
variables: {
|
|
72
72
|
limit: 50,
|
|
73
73
|
offset: 0
|
|
@@ -75,18 +75,18 @@ var w = () => {
|
|
|
75
75
|
authToken: x || void 0
|
|
76
76
|
});
|
|
77
77
|
if (e.errors?.length) throw Error(e.errors[0]?.message ?? "Failed to load version updates");
|
|
78
|
-
|
|
78
|
+
we((e.data?.applicationsWithPendingVersions.edges ?? []).map((e) => e.node)), Te(e.data?.applicationsWithPendingVersions.totalCount ?? 0);
|
|
79
79
|
} catch (e) {
|
|
80
80
|
M(e instanceof Error ? e.message : "Failed to load version updates");
|
|
81
81
|
} finally {
|
|
82
82
|
A(!1);
|
|
83
83
|
}
|
|
84
|
-
}, [x]),
|
|
85
|
-
|
|
84
|
+
}, [x]), je = async (e, t, n) => {
|
|
85
|
+
K(e), M(null);
|
|
86
86
|
try {
|
|
87
87
|
let t = await g({
|
|
88
88
|
gateway: "global",
|
|
89
|
-
query:
|
|
89
|
+
query: _e,
|
|
90
90
|
variables: {
|
|
91
91
|
id: e,
|
|
92
92
|
notes: n || null
|
|
@@ -98,18 +98,18 @@ var w = () => {
|
|
|
98
98
|
} catch (e) {
|
|
99
99
|
M(e instanceof Error ? e.message : "Approval failed");
|
|
100
100
|
} finally {
|
|
101
|
-
|
|
101
|
+
K(null);
|
|
102
102
|
}
|
|
103
|
-
},
|
|
103
|
+
}, Me = async (e, t, n) => {
|
|
104
104
|
if (!n.trim()) {
|
|
105
105
|
M("Rejection reason is required");
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
K(e), M(null);
|
|
109
109
|
try {
|
|
110
110
|
let t = await g({
|
|
111
111
|
gateway: "global",
|
|
112
|
-
query:
|
|
112
|
+
query: ve,
|
|
113
113
|
variables: {
|
|
114
114
|
id: e,
|
|
115
115
|
reason: n.trim()
|
|
@@ -117,11 +117,11 @@ var w = () => {
|
|
|
117
117
|
authToken: x || void 0
|
|
118
118
|
});
|
|
119
119
|
if (t.errors?.length) throw Error(t.errors[0]?.message ?? "Rejection failed");
|
|
120
|
-
|
|
120
|
+
q(null), Y(""), X(null), await Q();
|
|
121
121
|
} catch (e) {
|
|
122
122
|
M(e instanceof Error ? e.message : "Rejection failed");
|
|
123
123
|
} finally {
|
|
124
|
-
|
|
124
|
+
K(null);
|
|
125
125
|
}
|
|
126
126
|
}, $ = r(async (e) => {
|
|
127
127
|
if (e === "VERSION_UPDATES") {
|
|
@@ -172,7 +172,7 @@ var w = () => {
|
|
|
172
172
|
e.length !== 0 && (async () => {
|
|
173
173
|
let t = await Promise.allSettled(e.map((e) => g({
|
|
174
174
|
gateway: "global",
|
|
175
|
-
query:
|
|
175
|
+
query: me,
|
|
176
176
|
variables: { id: e.productId },
|
|
177
177
|
authToken: x || void 0
|
|
178
178
|
})));
|
|
@@ -190,12 +190,12 @@ var w = () => {
|
|
|
190
190
|
});
|
|
191
191
|
})();
|
|
192
192
|
}, [T, x]);
|
|
193
|
-
let
|
|
194
|
-
|
|
193
|
+
let Ne = async (e) => {
|
|
194
|
+
Ae(e);
|
|
195
195
|
try {
|
|
196
196
|
let t = (await g({
|
|
197
197
|
gateway: "global",
|
|
198
|
-
query:
|
|
198
|
+
query: he,
|
|
199
199
|
variables: { id: e },
|
|
200
200
|
authToken: x || void 0
|
|
201
201
|
})).data?.refreshApplicationVersionPackageUrl;
|
|
@@ -203,9 +203,9 @@ var w = () => {
|
|
|
203
203
|
} catch (e) {
|
|
204
204
|
M(e instanceof Error ? e.message : "Failed to refresh download URL");
|
|
205
205
|
} finally {
|
|
206
|
-
|
|
206
|
+
Ae(null);
|
|
207
207
|
}
|
|
208
|
-
},
|
|
208
|
+
}, Pe = async (e) => {
|
|
209
209
|
P(e), M(null);
|
|
210
210
|
try {
|
|
211
211
|
let t = await g({
|
|
@@ -224,7 +224,7 @@ var w = () => {
|
|
|
224
224
|
} finally {
|
|
225
225
|
P(null);
|
|
226
226
|
}
|
|
227
|
-
},
|
|
227
|
+
}, Fe = async (e) => {
|
|
228
228
|
if (!L.trim()) {
|
|
229
229
|
M("Rejection reason is required");
|
|
230
230
|
return;
|
|
@@ -247,7 +247,26 @@ var w = () => {
|
|
|
247
247
|
} finally {
|
|
248
248
|
P(null);
|
|
249
249
|
}
|
|
250
|
-
},
|
|
250
|
+
}, Ie = async (e) => {
|
|
251
|
+
P(e), M(null);
|
|
252
|
+
try {
|
|
253
|
+
let t = await g({
|
|
254
|
+
gateway: "global",
|
|
255
|
+
query: pe,
|
|
256
|
+
variables: {
|
|
257
|
+
id: e,
|
|
258
|
+
reason: L.trim() || void 0
|
|
259
|
+
},
|
|
260
|
+
authToken: x || void 0
|
|
261
|
+
});
|
|
262
|
+
if (t.errors?.length) throw Error(t.errors[0]?.message ?? "Delist failed");
|
|
263
|
+
I(null), R(""), await $(F);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
M(e instanceof Error ? e.message : "Delist failed");
|
|
266
|
+
} finally {
|
|
267
|
+
P(null);
|
|
268
|
+
}
|
|
269
|
+
}, Le = T.filter((e) => e.status === "PENDING_REVIEW").length;
|
|
251
270
|
return /* @__PURE__ */ v("div", {
|
|
252
271
|
className: "min-h-screen bg-[var(--color-surface-default)]",
|
|
253
272
|
children: [
|
|
@@ -286,19 +305,19 @@ var w = () => {
|
|
|
286
305
|
})]
|
|
287
306
|
}), /* @__PURE__ */ _("div", {
|
|
288
307
|
className: "mt-3 flex gap-1.5 flex-wrap",
|
|
289
|
-
children:
|
|
308
|
+
children: ye.map((e) => /* @__PURE__ */ v("button", {
|
|
290
309
|
type: "button",
|
|
291
|
-
onClick: () =>
|
|
310
|
+
onClick: () => Se(e.value),
|
|
292
311
|
className: `rounded-full px-3.5 py-1 text-xs font-medium transition-colors ${F === e.value ? "bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]" : "bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]"}`,
|
|
293
312
|
children: [
|
|
294
313
|
e.label,
|
|
295
|
-
e.value === "PENDING_REVIEW" &&
|
|
314
|
+
e.value === "PENDING_REVIEW" && Le > 0 && F !== "PENDING_REVIEW" && /* @__PURE__ */ _("span", {
|
|
296
315
|
className: "ml-1.5 rounded-full bg-[var(--color-status-warning-text)] px-1.5 py-0.5 text-[10px] text-white",
|
|
297
|
-
children:
|
|
316
|
+
children: Le
|
|
298
317
|
}),
|
|
299
|
-
e.value === "VERSION_UPDATES" &&
|
|
318
|
+
e.value === "VERSION_UPDATES" && W > 0 && F !== "VERSION_UPDATES" && /* @__PURE__ */ _("span", {
|
|
300
319
|
className: "ml-1.5 rounded-full bg-[var(--color-status-info-text)] px-1.5 py-0.5 text-[10px] text-white",
|
|
301
|
-
children:
|
|
320
|
+
children: W
|
|
302
321
|
})
|
|
303
322
|
]
|
|
304
323
|
}, e.value))
|
|
@@ -406,11 +425,11 @@ var w = () => {
|
|
|
406
425
|
className: "flex flex-wrap gap-2",
|
|
407
426
|
children: [t.packageUrl && /* @__PURE__ */ v("button", {
|
|
408
427
|
type: "button",
|
|
409
|
-
onClick: () => void
|
|
410
|
-
disabled:
|
|
428
|
+
onClick: () => void Ne(t.id),
|
|
429
|
+
disabled: Z === t.id,
|
|
411
430
|
className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors disabled:opacity-50",
|
|
412
431
|
children: [
|
|
413
|
-
|
|
432
|
+
Z === t.id ? /* @__PURE__ */ _(m, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ _(u, { className: "size-3.5" }),
|
|
414
433
|
"Download Package",
|
|
415
434
|
t.packageSize != null && /* @__PURE__ */ v("span", {
|
|
416
435
|
className: "text-[var(--color-text-muted)]",
|
|
@@ -444,8 +463,8 @@ var w = () => {
|
|
|
444
463
|
children: "Rejection reason (the developer will see this)"
|
|
445
464
|
}),
|
|
446
465
|
/* @__PURE__ */ _("textarea", {
|
|
447
|
-
value:
|
|
448
|
-
onChange: (e) =>
|
|
466
|
+
value: J,
|
|
467
|
+
onChange: (e) => Y(e.target.value),
|
|
449
468
|
rows: 2,
|
|
450
469
|
className: "w-full rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none",
|
|
451
470
|
placeholder: "Be specific — what needs to change before this version can be approved…"
|
|
@@ -455,14 +474,14 @@ var w = () => {
|
|
|
455
474
|
children: [/* @__PURE__ */ _("button", {
|
|
456
475
|
type: "button",
|
|
457
476
|
onClick: () => {
|
|
458
|
-
|
|
477
|
+
q(null), Y(""), X(null);
|
|
459
478
|
},
|
|
460
479
|
className: "rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors",
|
|
461
480
|
children: "Cancel"
|
|
462
481
|
}), /* @__PURE__ */ _("button", {
|
|
463
482
|
type: "button",
|
|
464
|
-
onClick: () => void
|
|
465
|
-
disabled: !
|
|
483
|
+
onClick: () => void Me(t.id, e.id, J),
|
|
484
|
+
disabled: !J.trim() || G === t.id,
|
|
466
485
|
className: "rounded-md bg-[var(--color-surface-danger)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-danger)] hover:opacity-90 disabled:opacity-50 transition-opacity",
|
|
467
486
|
children: "Submit rejection"
|
|
468
487
|
})]
|
|
@@ -473,16 +492,16 @@ var w = () => {
|
|
|
473
492
|
className: "flex items-center gap-2",
|
|
474
493
|
children: [/* @__PURE__ */ v("button", {
|
|
475
494
|
type: "button",
|
|
476
|
-
onClick: () => void
|
|
477
|
-
disabled:
|
|
495
|
+
onClick: () => void je(t.id, e.id, ""),
|
|
496
|
+
disabled: G === t.id,
|
|
478
497
|
className: "inline-flex items-center gap-1.5 rounded-md bg-[var(--color-surface-success)] px-4 py-1.5 text-xs font-semibold text-[var(--color-text-on-success)] hover:opacity-90 disabled:opacity-50 transition-opacity",
|
|
479
|
-
children: [
|
|
498
|
+
children: [G === t.id ? /* @__PURE__ */ _(m, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ _(o, { className: "size-3.5" }), "Approve version"]
|
|
480
499
|
}), Ee !== t.id && /* @__PURE__ */ v("button", {
|
|
481
500
|
type: "button",
|
|
482
501
|
onClick: () => {
|
|
483
|
-
|
|
502
|
+
q(t.id), Y(""), X(e.id);
|
|
484
503
|
},
|
|
485
|
-
disabled:
|
|
504
|
+
disabled: G === t.id,
|
|
486
505
|
className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-1.5 text-xs font-semibold text-[var(--color-text-danger)] hover:bg-[var(--color-surface-danger)] hover:text-[var(--color-text-on-danger)] disabled:opacity-50 transition-colors",
|
|
487
506
|
children: [/* @__PURE__ */ _(h, { className: "size-3.5" }), "Reject version"]
|
|
488
507
|
})]
|
|
@@ -524,11 +543,11 @@ var w = () => {
|
|
|
524
543
|
children: e.name
|
|
525
544
|
}),
|
|
526
545
|
/* @__PURE__ */ _("span", {
|
|
527
|
-
className: `rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide ${
|
|
546
|
+
className: `rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide ${be(e.type)}`,
|
|
528
547
|
children: e.type
|
|
529
548
|
}),
|
|
530
549
|
/* @__PURE__ */ _("span", {
|
|
531
|
-
className: `rounded-full px-2 py-0.5 text-[11px] font-medium ${
|
|
550
|
+
className: `rounded-full px-2 py-0.5 text-[11px] font-medium ${xe(e.status)}`,
|
|
532
551
|
children: e.status.replace("_", " ")
|
|
533
552
|
})
|
|
534
553
|
]
|
|
@@ -590,10 +609,10 @@ var w = () => {
|
|
|
590
609
|
S.has(e.type) && (() => {
|
|
591
610
|
let t = e.productId ? Oe[e.productId]?.versionId ?? U.find((t) => t.id === e.productId)?.versions?.edges?.[0]?.node?.id : void 0;
|
|
592
611
|
if (!t && !e.zipFileUrl) return null;
|
|
593
|
-
let n =
|
|
612
|
+
let n = Z === t;
|
|
594
613
|
return t ? /* @__PURE__ */ v("button", {
|
|
595
614
|
type: "button",
|
|
596
|
-
onClick: () => void
|
|
615
|
+
onClick: () => void Ne(t),
|
|
597
616
|
disabled: n,
|
|
598
617
|
className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors disabled:opacity-50",
|
|
599
618
|
children: [n ? /* @__PURE__ */ _(m, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ _(u, { className: "size-3.5" }), "Download Package"]
|
|
@@ -625,21 +644,33 @@ var w = () => {
|
|
|
625
644
|
]
|
|
626
645
|
}), /* @__PURE__ */ v("div", {
|
|
627
646
|
className: "flex items-center gap-2",
|
|
628
|
-
children: [
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
647
|
+
children: [
|
|
648
|
+
e.status !== "PUBLISHED" && /* @__PURE__ */ v("button", {
|
|
649
|
+
type: "button",
|
|
650
|
+
onClick: () => void Pe(e.id),
|
|
651
|
+
disabled: N === e.id,
|
|
652
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-[var(--color-surface-success)] px-4 py-1.5 text-xs font-semibold text-[var(--color-text-on-success)] hover:opacity-90 disabled:opacity-50 transition-opacity",
|
|
653
|
+
children: [/* @__PURE__ */ _(o, { className: "size-3.5" }), "Approve"]
|
|
654
|
+
}),
|
|
655
|
+
e.status === "PENDING_REVIEW" && /* @__PURE__ */ v("button", {
|
|
656
|
+
type: "button",
|
|
657
|
+
onClick: () => {
|
|
658
|
+
I(e.id), R("");
|
|
659
|
+
},
|
|
660
|
+
disabled: N === e.id,
|
|
661
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)] px-4 py-1.5 text-xs font-semibold text-[var(--color-text-danger)] hover:bg-[var(--color-surface-danger)] hover:text-[var(--color-text-on-danger)] disabled:opacity-50 transition-colors",
|
|
662
|
+
children: [/* @__PURE__ */ _(h, { className: "size-3.5" }), "Reject"]
|
|
663
|
+
}),
|
|
664
|
+
e.status === "PUBLISHED" && /* @__PURE__ */ v("button", {
|
|
665
|
+
type: "button",
|
|
666
|
+
onClick: () => {
|
|
667
|
+
I(e.id), R("");
|
|
668
|
+
},
|
|
669
|
+
disabled: N === e.id,
|
|
670
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-warning)] bg-[var(--color-surface-warning-muted)] px-4 py-1.5 text-xs font-semibold text-[var(--color-text-warning)] hover:bg-[var(--color-surface-warning)] hover:text-[var(--color-text-on-warning)] disabled:opacity-50 transition-colors",
|
|
671
|
+
children: [/* @__PURE__ */ _(h, { className: "size-3.5" }), "Delist"]
|
|
672
|
+
})
|
|
673
|
+
]
|
|
643
674
|
})]
|
|
644
675
|
}),
|
|
645
676
|
z === e.id && /* @__PURE__ */ v("div", {
|
|
@@ -733,23 +764,26 @@ var w = () => {
|
|
|
733
764
|
})] })
|
|
734
765
|
]
|
|
735
766
|
}),
|
|
736
|
-
|
|
737
|
-
className:
|
|
767
|
+
Ce === e.id && /* @__PURE__ */ v("div", {
|
|
768
|
+
className: `border-t border-[var(--color-border-seam)] px-5 py-4 ${e.status === "PUBLISHED" ? "bg-[var(--color-surface-warning-muted)]/30" : "bg-[var(--color-surface-danger-muted)]/30"}`,
|
|
738
769
|
children: [
|
|
739
|
-
/* @__PURE__ */ _("p", {
|
|
770
|
+
e.status === "PUBLISHED" ? /* @__PURE__ */ _("p", {
|
|
771
|
+
className: "mb-2 text-xs font-semibold text-[var(--color-text-warning)]",
|
|
772
|
+
children: "Delist — this removes the product from the store for new users. Existing installs keep working. Reason is optional."
|
|
773
|
+
}) : /* @__PURE__ */ _("p", {
|
|
740
774
|
className: "mb-2 text-xs font-semibold text-[var(--color-text-danger)]",
|
|
741
775
|
children: "Reject — provide a reason (the publisher will see this)"
|
|
742
776
|
}),
|
|
743
777
|
/* @__PURE__ */ _("textarea", {
|
|
744
778
|
id: `reject-reason-${e.id}`,
|
|
745
|
-
"aria-label": "Rejection reason",
|
|
779
|
+
"aria-label": e.status === "PUBLISHED" ? "Delist reason" : "Rejection reason",
|
|
746
780
|
value: L,
|
|
747
781
|
onChange: (e) => R(e.target.value),
|
|
748
782
|
rows: 3,
|
|
749
|
-
required:
|
|
750
|
-
"aria-required": "
|
|
783
|
+
required: e.status !== "PUBLISHED",
|
|
784
|
+
"aria-required": e.status !== "PUBLISHED",
|
|
751
785
|
className: "w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus:border-[var(--color-border-accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none",
|
|
752
|
-
placeholder: "Be specific — e.g. 'Manifest is missing the identity.version field' or 'ZIP file is empty'…"
|
|
786
|
+
placeholder: e.status === "PUBLISHED" ? "Optional — reason for delisting…" : "Be specific — e.g. 'Manifest is missing the identity.version field' or 'ZIP file is empty'…"
|
|
753
787
|
}),
|
|
754
788
|
/* @__PURE__ */ v("div", {
|
|
755
789
|
className: "mt-2 flex items-center justify-end gap-2",
|
|
@@ -760,9 +794,15 @@ var w = () => {
|
|
|
760
794
|
},
|
|
761
795
|
className: "rounded-md border border-[var(--color-border-default)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors",
|
|
762
796
|
children: "Cancel"
|
|
763
|
-
}), /* @__PURE__ */ _("button", {
|
|
797
|
+
}), e.status === "PUBLISHED" ? /* @__PURE__ */ _("button", {
|
|
764
798
|
type: "button",
|
|
765
|
-
onClick: () => void
|
|
799
|
+
onClick: () => void Ie(e.id),
|
|
800
|
+
disabled: N === e.id,
|
|
801
|
+
className: "rounded-md bg-[var(--color-surface-warning)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-warning)] hover:opacity-90 disabled:opacity-50 transition-opacity",
|
|
802
|
+
children: "Confirm delist"
|
|
803
|
+
}) : /* @__PURE__ */ _("button", {
|
|
804
|
+
type: "button",
|
|
805
|
+
onClick: () => void Fe(e.id),
|
|
766
806
|
disabled: !L.trim() || N === e.id,
|
|
767
807
|
className: "rounded-md bg-[var(--color-surface-danger)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text-on-danger)] hover:opacity-90 disabled:opacity-50 transition-opacity",
|
|
768
808
|
children: "Submit rejection"
|