@burdenoff/microfe-store 2026.731.1 → 2026.805.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.
Files changed (35) hide show
  1. package/dist/StoreAdminRoutes.js +1 -1
  2. package/dist/StoreAdminRoutes.js.map +1 -1
  3. package/dist/StoreRoutes.js +1 -1
  4. package/dist/StoreRoutes.js.map +1 -1
  5. package/dist/components/InstallAppModal.js +211 -137
  6. package/dist/components/InstallAppModal.js.map +1 -1
  7. package/dist/generated/global-types.d.ts +6 -0
  8. package/dist/generated/global-types.js.map +1 -1
  9. package/dist/pages/AdminDashboardPage.js +1 -1
  10. package/dist/pages/AdminDashboardPage.js.map +1 -1
  11. package/dist/pages/AdminReviewModerationPage.js +1 -1
  12. package/dist/pages/AdminReviewModerationPage.js.map +1 -1
  13. package/dist/pages/AdminStoresPage.js +1 -1
  14. package/dist/pages/AdminStoresPage.js.map +1 -1
  15. package/dist/pages/AdminSubmissionsQueuePage.js +1 -1
  16. package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
  17. package/dist/pages/AppDetailPage.js +1 -1
  18. package/dist/pages/AppDetailPage.js.map +1 -1
  19. package/dist/pages/AppSettingsPage.js +1 -1
  20. package/dist/pages/AppSettingsPage.js.map +1 -1
  21. package/dist/pages/CartPage.js +1 -1
  22. package/dist/pages/CartPage.js.map +1 -1
  23. package/dist/pages/InstalledAppsPage.js +1 -1
  24. package/dist/pages/InstalledAppsPage.js.map +1 -1
  25. package/dist/pages/MarketplaceHomePage.js +1 -1
  26. package/dist/pages/MarketplaceHomePage.js.map +1 -1
  27. package/dist/pages/MyLicensesPage.js +1 -1
  28. package/dist/pages/MyLicensesPage.js.map +1 -1
  29. package/dist/pages/OrderConfirmationPage.js +1 -1
  30. package/dist/pages/OrderConfirmationPage.js.map +1 -1
  31. package/dist/pages/OrdersPage.js +1 -1
  32. package/dist/pages/OrdersPage.js.map +1 -1
  33. package/dist/pages/ProductDetailPage.js +1 -1
  34. package/dist/pages/ProductDetailPage.js.map +1 -1
  35. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"AdminSubmissionsQueuePage.js","names":[],"sources":["../../src/pages/AdminSubmissionsQueuePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n Check,\n X,\n RefreshCw,\n PackageCheck,\n ChevronDown,\n ChevronUp,\n ExternalLink,\n FileJson,\n ShieldCheck,\n FlaskConical,\n Download,\n Clock,\n Tag,\n User,\n Hash,\n GitBranch,\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';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport Markdown from 'react-markdown';\nimport { InstallAppModal } from '../components/InstallAppModal';\n\ntype PendingProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature: string;\n status: string;\n pricingModel: string;\n price: number;\n currency: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n category?: string | null;\n icon?: string | null;\n productId?: string | null;\n zipFileUrl?: string | null;\n manifest?: Record<string, unknown> | null;\n requiredPermissions?: string[] | null;\n publisher?: { id: string; name: string; email: string } | null;\n};\n\ntype ProductStatus = 'PENDING_REVIEW' | 'PUBLISHED' | 'REJECTED' | 'ALL' | 'VERSION_UPDATES';\n\ntype PendingVersion = {\n id: string;\n version: string;\n status: string;\n releaseNotes?: string | null;\n packageUrl?: string | null;\n packageSize?: number | null;\n isLatest?: boolean | null;\n reviewNotes?: string | null;\n manifest?: Record<string, unknown> | null;\n createdAt: string;\n};\n\ntype VersionUpdateApp = {\n id: string;\n name: string;\n slug: string;\n iconUrl?: string | null;\n shortDescription?: string | null;\n description?: string | null;\n versions: { edges: Array<{ node: PendingVersion }> };\n};\n\nconst PENDING_PRODUCTS_QUERY = /* GraphQL */ `\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`;\n\nconst ALL_PRODUCTS_QUERY = /* GraphQL */ `\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`;\n\nconst APPROVE_PRODUCT_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!, $notes: String) {\n approveProduct(id: $id, notes: $notes) {\n id\n status\n publishedAt\n }\n }\n`;\n\nconst REJECT_PRODUCT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n rejectedReason\n }\n }\n`;\n\nconst DELIST_PRODUCT_MUTATION = /* GraphQL */ `\n mutation DelistProduct($id: ID!, $reason: String) {\n delistProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst APP_PACKAGE_URL_QUERY = /* GraphQL */ `\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`;\n\nconst REFRESH_PACKAGE_URL_MUTATION = /* GraphQL */ `\n mutation RefreshApplicationVersionPackageUrl($id: ID!) {\n refreshApplicationVersionPackageUrl(id: $id)\n }\n`;\n\nconst VERSION_UPDATES_QUERY = /* GraphQL */ `\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`;\n\nconst APPROVE_VERSION_MUTATION = /* GraphQL */ `\n mutation AdminApproveApplicationVersion($id: ID!, $notes: String) {\n approveApplicationVersion(id: $id, notes: $notes) {\n id\n status\n }\n }\n`;\n\nconst REJECT_VERSION_MUTATION = /* GraphQL */ `\n mutation AdminRejectApplicationVersion($id: ID!, $reason: String!) {\n rejectApplicationVersion(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst STATUS_FILTERS: Array<{ label: string; value: ProductStatus }> = [\n { label: 'Pending review', value: 'PENDING_REVIEW' },\n { label: 'Published', value: 'PUBLISHED' },\n { label: 'Rejected', value: 'REJECTED' },\n { label: 'Version Updates', value: 'VERSION_UPDATES' },\n { label: 'All', value: 'ALL' },\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\nconst STATUS_COLORS: Record<string, string> = {\n PENDING_REVIEW:\n 'bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)]',\n PUBLISHED: 'bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)]',\n REJECTED: 'bg-[var(--color-status-error-bg-subtle)] text-[var(--color-status-error-text)]',\n};\n\nconst PACKAGE_TYPES = new Set(['NODE', 'WORKFLOW', 'VIBEMODULE', 'PARSER', 'DATASINK', 'CONSOLE']);\n\nfunction typeColor(type: string) {\n return TYPE_COLORS[type] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n}\n\nfunction statusColor(status: string) {\n return (\n STATUS_COLORS[status] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]'\n );\n}\n\nfunction formatDate(iso?: string | null) {\n if (!iso) return '—';\n return new Date(iso).toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport const AdminSubmissionsQueuePage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n const [products, setProducts] = useState<PendingProduct[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [actingId, setActingId] = useState<string | null>(null);\n const [statusFilter, setStatusFilter] = useState<ProductStatus>('PENDING_REVIEW');\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [viewingId, setViewingId] = useState<string | null>(null);\n const [testingProduct, setTestingProduct] = useState<PendingProduct | null>(null);\n const [versionApps, setVersionApps] = useState<VersionUpdateApp[]>([]);\n const [versionUpdateCount, setVersionUpdateCount] = useState(0);\n const [versionActingId, setVersionActingId] = useState<string | null>(null);\n const [versionRejectingId, setVersionRejectingId] = useState<string | null>(null);\n const [versionRejectReason, setVersionRejectReason] = useState('');\n const [versionRejectAppId, setVersionRejectAppId] = useState<string | null>(null);\n // productId → { versionId, packageUrl } for legacy submissions where zipFileUrl was not populated\n const [appVersionInfo, setAppVersionInfo] = useState<\n Record<string, { versionId: string; packageUrl: string }>\n >({});\n const [downloadingVersionId, setDownloadingVersionId] = useState<string | null>(null);\n\n const loadVersionApps = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await graphqlFetch<{\n applicationsWithPendingVersions: {\n totalCount: number;\n edges: Array<{ node: VersionUpdateApp }>;\n };\n }>({\n gateway: 'global',\n query: VERSION_UPDATES_QUERY,\n variables: { limit: 50, offset: 0 },\n authToken: authToken || undefined,\n });\n if (result.errors?.length)\n throw new Error(result.errors[0]?.message ?? 'Failed to load version updates');\n const edges = result.data?.applicationsWithPendingVersions.edges ?? [];\n setVersionApps(edges.map((e) => e.node));\n setVersionUpdateCount(result.data?.applicationsWithPendingVersions.totalCount ?? 0);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load version updates');\n } finally {\n setLoading(false);\n }\n }, [authToken]);\n\n const approveVersion = async (versionId: string, appId: string, notes: string) => {\n setVersionActingId(versionId);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_VERSION_MUTATION,\n variables: { id: versionId, notes: notes || null },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) throw new Error(result.errors[0]?.message ?? 'Approval failed');\n await loadVersionApps();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setVersionActingId(null);\n }\n };\n\n const rejectVersion = async (versionId: string, appId: string, reason: string) => {\n if (!reason.trim()) {\n setError('Rejection reason is required');\n return;\n }\n setVersionActingId(versionId);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_VERSION_MUTATION,\n variables: { id: versionId, reason: reason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n setVersionRejectingId(null);\n setVersionRejectReason('');\n setVersionRejectAppId(null);\n await loadVersionApps();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setVersionActingId(null);\n }\n };\n\n const loadProducts = useCallback(\n async (filter: ProductStatus) => {\n if (filter === 'VERSION_UPDATES') {\n await loadVersionApps();\n return;\n }\n setLoading(true);\n setError(null);\n try {\n if (filter === 'PENDING_REVIEW') {\n const result = await graphqlFetch<{\n pendingReviewProducts: { totalCount: number; products: PendingProduct[] };\n }>({\n gateway: 'global',\n query: PENDING_PRODUCTS_QUERY,\n variables: { limit: 100, offset: 0 },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load pending products');\n }\n setProducts(result.data?.pendingReviewProducts.products ?? []);\n setTotalCount(result.data?.pendingReviewProducts.totalCount ?? 0);\n } else {\n const result = await graphqlFetch<{ products: PendingProduct[] }>({\n gateway: 'global',\n query: ALL_PRODUCTS_QUERY,\n variables: {\n limit: 100,\n offset: 0,\n ...(filter !== 'ALL' && { status: filter }),\n },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load products');\n }\n const list = result.data?.products ?? [];\n setProducts(list);\n setTotalCount(list.length);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setLoading(false);\n }\n },\n [authToken, loadVersionApps]\n );\n\n useEffect(() => {\n void loadProducts(statusFilter);\n }, [loadProducts, statusFilter]);\n\n // Keep version update count fresh for the badge even when not on that tab\n useEffect(() => {\n void loadVersionApps();\n }, [loadVersionApps]);\n\n // For PACKAGE_TYPES products whose zipFileUrl was not populated at submission time,\n // fetch the latest version ID + package URL directly from the devportal.\n useEffect(() => {\n const targets = products.filter(\n (p) => PACKAGE_TYPES.has(p.type) && p.productId && !p.zipFileUrl\n );\n if (targets.length === 0) return;\n void (async () => {\n const results = await Promise.allSettled(\n targets.map((p) =>\n graphqlFetch<{\n application: {\n id: string;\n versions: { edges: Array<{ node: { id: string; packageUrl?: string | null } }> };\n } | null;\n }>({\n gateway: 'global',\n query: APP_PACKAGE_URL_QUERY,\n variables: { id: p.productId },\n authToken: authToken || undefined,\n })\n )\n );\n setAppVersionInfo((prev) => {\n const next = { ...prev };\n results.forEach((r, i) => {\n if (r.status === 'fulfilled') {\n const node = r.value.data?.application?.versions?.edges?.[0]?.node;\n const productId = targets[i].productId;\n if (node?.id && node.packageUrl && productId) {\n next[productId] = { versionId: node.id, packageUrl: node.packageUrl };\n }\n }\n });\n return next;\n });\n })();\n }, [products, authToken]);\n\n const downloadPackage = async (versionId: string) => {\n setDownloadingVersionId(versionId);\n try {\n const result = await graphqlFetch<{ refreshApplicationVersionPackageUrl: string | null }>({\n gateway: 'global',\n query: REFRESH_PACKAGE_URL_MUTATION,\n variables: { id: versionId },\n authToken: authToken || undefined,\n });\n const freshUrl = result.data?.refreshApplicationVersionPackageUrl;\n if (freshUrl) {\n window.open(freshUrl, '_blank', 'noreferrer');\n } else {\n setError('Could not generate a download URL. S3 credentials may not be configured.');\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to refresh download URL');\n } finally {\n setDownloadingVersionId(null);\n }\n };\n\n const approve = async (id: string) => {\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_PRODUCT_MUTATION,\n variables: { id, notes: null },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await loadProducts(statusFilter);\n } catch (err) {\n setError(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()) {\n setError('Rejection reason is required');\n return;\n }\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_PRODUCT_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 loadProducts(statusFilter);\n } catch (err) {\n setError(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 setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: DELIST_PRODUCT_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 loadProducts(statusFilter);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Delist failed');\n } finally {\n setActingId(null);\n }\n };\n\n const pendingCount = products.filter((p) => p.status === 'PENDING_REVIEW').length;\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-5xl px-6 py-4\">\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/dashboard`)}\n className=\"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-sm text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n <ArrowLeft className=\"size-4 shrink-0\" />\n Back\n </button>\n <div className=\"h-5 w-px bg-[var(--color-border-seam)]\" />\n <PackageCheck className=\"size-5 shrink-0 text-[var(--color-text-primary)]\" />\n <h1 className=\"text-lg font-semibold text-[var(--color-text-primary)] truncate\">\n Submission Queue\n </h1>\n {totalCount > 0 && (\n <span className=\"shrink-0 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 {totalCount}\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => void loadProducts(statusFilter)}\n className=\"shrink-0 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 disabled={loading}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n\n {/* Status filter pills */}\n <div className=\"mt-3 flex gap-1.5 flex-wrap\">\n {STATUS_FILTERS.map((f) => (\n <button\n key={f.value}\n type=\"button\"\n onClick={() => setStatusFilter(f.value)}\n className={`rounded-full px-3.5 py-1 text-xs font-medium transition-colors ${\n statusFilter === f.value\n ? 'bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]'\n : 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]'\n }`}\n >\n {f.label}\n {f.value === 'PENDING_REVIEW' &&\n pendingCount > 0 &&\n statusFilter !== 'PENDING_REVIEW' && (\n <span className=\"ml-1.5 rounded-full bg-[var(--color-status-warning-text)] px-1.5 py-0.5 text-[10px] text-white\">\n {pendingCount}\n </span>\n )}\n {f.value === 'VERSION_UPDATES' &&\n versionUpdateCount > 0 &&\n statusFilter !== 'VERSION_UPDATES' && (\n <span className=\"ml-1.5 rounded-full bg-[var(--color-status-info-text)] px-1.5 py-0.5 text-[10px] text-white\">\n {versionUpdateCount}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-5xl space-y-4 p-6\">\n <PagePurpose className=\"mb-2\">\n Every publisher submission lands here for your review. Inspect the manifest, permissions,\n and package details — then approve to publish or reject with a clear reason the developer\n will see.\n </PagePurpose>\n\n {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 {error}\n </div>\n )}\n\n {statusFilter === 'VERSION_UPDATES' ? (\n loading && versionApps.length === 0 ? (\n <div className=\"flex items-center justify-center py-24\">\n <RefreshCw className=\"size-6 animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : versionApps.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No pending version updates\"\n description=\"All published apps are up to date. When a developer submits a new version, it will appear here for your review.\"\n />\n ) : (\n <ul className=\"space-y-4\">\n {versionApps.map((app) => {\n const pendingVersions = app.versions.edges\n .map((e) => e.node)\n .filter((v) => v.status === 'pending_review');\n if (pendingVersions.length === 0) return null;\n return (\n <li\n key={app.id}\n className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\"\n >\n {/* App header */}\n <div className=\"flex items-center gap-4 p-5 border-b border-[var(--color-border-seam)]\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt=\"\"\n className=\"size-12 shrink-0 rounded-xl object-cover shadow-sm\"\n />\n ) : (\n <div className=\"flex size-12 shrink-0 items-center justify-center rounded-xl bg-[var(--color-surface-muted)] text-sm font-bold text-[var(--color-text-muted)] uppercase\">\n {app.name.slice(0, 2)}\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n {app.name}\n </h3>\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-status-info-bg-subtle)] px-2 py-0.5 text-[11px] font-semibold text-[var(--color-status-info-text)]\">\n <GitBranch className=\"size-3\" />\n {pendingVersions.length} version\n {pendingVersions.length !== 1 ? 's' : ''} pending\n </span>\n </div>\n {(app.shortDescription ?? app.description) && (\n <p className=\"mt-0.5 text-sm text-[var(--color-text-secondary)] line-clamp-1\">\n {app.shortDescription ?? app.description}\n </p>\n )}\n </div>\n <a\n href={`/devportal/apps/${app.id}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"shrink-0 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\"\n >\n <ExternalLink className=\"size-3.5\" />\n Devportal\n </a>\n </div>\n\n {/* Pending versions */}\n <div className=\"divide-y divide-[var(--color-border-seam)]\">\n {pendingVersions.map((ver) => (\n <div key={ver.id} className=\"p-5 space-y-3\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <GitBranch className=\"size-4 text-[var(--color-text-secondary)] shrink-0\" />\n <span className=\"font-mono font-semibold text-sm text-[var(--color-text-primary)]\">\n {ver.version}\n </span>\n {ver.isLatest && (\n <span className=\"rounded-full bg-[var(--color-surface-muted)] px-2 py-0.5 text-[10px] text-[var(--color-text-secondary)]\">\n Latest\n </span>\n )}\n <span className=\"rounded-full bg-[var(--color-status-warning-bg-subtle)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-status-warning-text)] uppercase\">\n New Version\n </span>\n <span className=\"ml-auto flex items-center gap-1 text-xs text-[var(--color-text-secondary)]\">\n <Clock className=\"size-3\" />\n {formatDate(ver.createdAt)}\n </span>\n </div>\n\n {ver.releaseNotes && (\n <div className=\"text-sm text-[var(--color-text-secondary)]\">\n <Markdown>{ver.releaseNotes}</Markdown>\n </div>\n )}\n\n <div className=\"flex flex-wrap gap-2\">\n {ver.packageUrl && (\n <button\n type=\"button\"\n onClick={() => void downloadPackage(ver.id)}\n disabled={downloadingVersionId === ver.id}\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)] transition-colors disabled:opacity-50\"\n >\n {downloadingVersionId === ver.id ? (\n <RefreshCw className=\"size-3.5 animate-spin\" />\n ) : (\n <Download className=\"size-3.5\" />\n )}\n Download Package\n {ver.packageSize != null && (\n <span className=\"text-[var(--color-text-muted)]\">\n ({ver.packageSize} bytes)\n </span>\n )}\n </button>\n )}\n {ver.manifest && (\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === ver.id ? null : ver.id)}\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)] transition-colors\"\n >\n <FileJson className=\"size-3.5\" />\n {viewingId === ver.id ? 'Hide manifest' : 'View manifest'}\n {viewingId === ver.id ? (\n <ChevronUp className=\"size-3\" />\n ) : (\n <ChevronDown className=\"size-3\" />\n )}\n </button>\n )}\n </div>\n\n {viewingId === ver.id && ver.manifest && (\n <pre className=\"overflow-x-auto rounded-lg border border-[var(--color-border-seam)] bg-[var(--color-surface-default)] p-4 text-[11px] leading-relaxed text-[var(--color-text-primary)] max-h-48 overflow-y-auto\">\n {JSON.stringify(ver.manifest, null, 2)}\n </pre>\n )}\n\n {/* Reject reason input */}\n {versionRejectingId === ver.id && versionRejectAppId === app.id && (\n <div className=\"rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)]/30 p-3 space-y-2\">\n <p className=\"text-xs font-semibold text-[var(--color-text-danger)]\">\n Rejection reason (the developer will see this)\n </p>\n <textarea\n value={versionRejectReason}\n onChange={(e) => setVersionRejectReason(e.target.value)}\n rows={2}\n 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\"\n placeholder=\"Be specific — what needs to change before this version can be approved…\"\n />\n <div className=\"flex gap-2 justify-end\">\n <button\n type=\"button\"\n onClick={() => {\n setVersionRejectingId(null);\n setVersionRejectReason('');\n setVersionRejectAppId(null);\n }}\n 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\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() =>\n void rejectVersion(ver.id, app.id, versionRejectReason)\n }\n disabled={\n !versionRejectReason.trim() || versionActingId === ver.id\n }\n 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\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => void approveVersion(ver.id, app.id, '')}\n disabled={versionActingId === ver.id}\n 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\"\n >\n {versionActingId === ver.id ? (\n <RefreshCw className=\"size-3.5 animate-spin\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n Approve version\n </button>\n {versionRejectingId !== ver.id && (\n <button\n type=\"button\"\n onClick={() => {\n setVersionRejectingId(ver.id);\n setVersionRejectReason('');\n setVersionRejectAppId(app.id);\n }}\n disabled={versionActingId === ver.id}\n 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\"\n >\n <X className=\"size-3.5\" />\n Reject version\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n </li>\n );\n })}\n </ul>\n )\n ) : loading && products.length === 0 ? (\n <div className=\"flex items-center justify-center py-24\">\n <RefreshCw className=\"size-6 animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : products.length === 0 ? (\n <IllustratedEmptyState\n illustration={statusFilter === 'PENDING_REVIEW' ? 'empty-notifications' : 'empty-data'}\n title={\n statusFilter === 'PENDING_REVIEW' ? 'Queue is clear' : 'No products match this filter'\n }\n description={\n statusFilter === 'PENDING_REVIEW'\n ? 'No submissions are awaiting review. New submissions will appear here as publishers send them in.'\n : 'Nothing matches the current filter. Try switching to a different status above.'\n }\n />\n ) : (\n <ul className=\"space-y-3\">\n {products.map((p) => (\n <li\n key={p.id}\n className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)] transition-[box-shadow,border-color] hover:border-[var(--color-border-strong)] hover:shadow-[var(--shadow-elevation-2)]\"\n >\n {/* Card body */}\n <div className=\"flex items-start gap-4 p-5\">\n {/* App icon */}\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-14 shrink-0 rounded-xl object-cover shadow-sm\"\n />\n ) : (\n <div className=\"flex size-14 shrink-0 items-center justify-center rounded-xl bg-[var(--color-surface-muted)] text-sm font-bold text-[var(--color-text-muted)] uppercase\">\n {p.type.slice(0, 2)}\n </div>\n )}\n\n {/* Main info */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n {p.name}\n </h3>\n <span\n className={`rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide ${typeColor(p.type)}`}\n >\n {p.type}\n </span>\n <span\n className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${statusColor(p.status)}`}\n >\n {p.status.replace('_', ' ')}\n </span>\n </div>\n\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-[var(--color-text-secondary)]\">\n <span className=\"flex items-center gap-1\">\n <Hash className=\"size-3\" />\n <span className=\"font-mono\">{p.slug}</span>\n </span>\n {p.publisher && (\n <span className=\"flex items-center gap-1\">\n <User className=\"size-3\" />\n {p.publisher.name}\n {p.publisher.email && (\n <span className=\"text-[var(--color-text-muted)]\">\n ({p.publisher.email})\n </span>\n )}\n </span>\n )}\n {p.submittedAt && (\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {formatDate(p.submittedAt)}\n </span>\n )}\n <span className=\"flex items-center gap-1\">\n <Tag className=\"size-3\" />\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}\n </span>\n </div>\n\n {p.description && (\n <p className=\"mt-2 line-clamp-2 text-sm text-[var(--color-text-secondary)]\">\n {p.description}\n </p>\n )}\n </div>\n </div>\n\n {/* Action bar */}\n <div className=\"flex flex-wrap items-center justify-between gap-2 border-t border-[var(--color-border-seam)] bg-[var(--color-surface-muted)] px-5 py-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {/* View in devportal */}\n {p.productId && (\n <a\n href={`/devportal/apps/${p.productId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\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 <ExternalLink className=\"size-3.5\" />\n Devportal\n </a>\n )}\n\n {/* Package download for NODE/WORKFLOW. Always calls the refresh mutation\n to get a fresh presigned URL (stored URLs expire in 1h). */}\n {PACKAGE_TYPES.has(p.type) &&\n (() => {\n // Resolve the version ID to use with the refresh mutation\n const versionId = p.productId\n ? (appVersionInfo[p.productId]?.versionId ??\n versionApps.find((va) => va.id === p.productId)?.versions?.edges?.[0]\n ?.node?.id)\n : undefined;\n // If we have no version ID yet, check if we at least have zipFileUrl\n // (new submissions populated by the backend fix)\n if (!versionId && !p.zipFileUrl) return null;\n const isLoading = downloadingVersionId === versionId;\n if (versionId) {\n return (\n <button\n type=\"button\"\n onClick={() => void downloadPackage(versionId)}\n disabled={isLoading}\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 disabled:opacity-50\"\n >\n {isLoading ? (\n <RefreshCw className=\"size-3.5 animate-spin\" />\n ) : (\n <Download className=\"size-3.5\" />\n )}\n Download Package\n </button>\n );\n }\n // Fallback: direct link when we have zipFileUrl but no versionId yet\n return (\n <a\n href={p.zipFileUrl!}\n download\n target=\"_blank\"\n rel=\"noreferrer\"\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 <Download className=\"size-3.5\" />\n Download Package\n </a>\n );\n })()}\n\n {/* Test in workspace for NODE/WORKFLOW */}\n {PACKAGE_TYPES.has(p.type) && (\n <button\n type=\"button\"\n onClick={() => setTestingProduct(p)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-accent)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-accent)] hover:bg-[var(--color-surface-accent-muted)] transition-colors\"\n >\n <FlaskConical className=\"size-3.5\" />\n Test in workspace\n </button>\n )}\n\n {/* Show manifest / details toggle */}\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\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)] transition-colors\"\n >\n <FileJson className=\"size-3.5\" />\n {viewingId === p.id ? 'Hide details' : 'Inspect details'}\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3\" />\n ) : (\n <ChevronDown className=\"size-3\" />\n )}\n </button>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {p.status !== 'PUBLISHED' && (\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n 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\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n )}\n {p.status === 'PENDING_REVIEW' && (\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n }}\n disabled={actingId === p.id}\n 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\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n )}\n {p.status === 'PUBLISHED' && (\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n }}\n disabled={actingId === p.id}\n 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\"\n >\n <X className=\"size-3.5\" />\n Delist\n </button>\n )}\n </div>\n </div>\n\n {/* Details panel */}\n {viewingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-muted)] p-5 space-y-5\">\n {/* Metadata grid */}\n <div 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)]\">\n Store product ID\n </dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)] break-all\">\n {p.id}\n </dd>\n </div>\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)]\">\n Type / Nature\n </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)]\">{p.category}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Pricing</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)]\">({p.pricingModel})</span>\n </dd>\n </div>\n {p.productId && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Devportal app ID\n </dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)] break-all\">\n {p.productId}\n </dd>\n </div>\n )}\n {p.submittedBy && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Submitted by (actor)\n </dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)] break-all\">\n {p.submittedBy}\n </dd>\n </div>\n )}\n </div>\n\n {/* Required permissions */}\n {p.requiredPermissions && p.requiredPermissions.length > 0 && (\n <div>\n <h4 className=\"mb-2 flex items-center gap-1.5 text-xs font-semibold text-[var(--color-text-secondary)] uppercase tracking-wide\">\n <ShieldCheck className=\"size-3.5 text-[var(--color-status-success-text)]\" />\n Required Permissions\n </h4>\n <div className=\"flex flex-wrap gap-1.5\">\n {p.requiredPermissions.map((perm) => (\n <span\n key={perm}\n className=\"inline-flex items-center gap-1 rounded-md bg-[var(--color-surface-default)] border border-[var(--color-border-seam)] px-2 py-1 text-xs font-mono text-[var(--color-text-primary)]\"\n >\n {perm}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Manifest JSON */}\n {p.manifest && (\n <div>\n <h4 className=\"mb-2 flex items-center gap-1.5 text-xs font-semibold text-[var(--color-text-secondary)] uppercase tracking-wide\">\n <FileJson className=\"size-3.5\" />\n Manifest\n </h4>\n <pre className=\"overflow-x-auto rounded-lg border border-[var(--color-border-seam)] bg-[var(--color-surface-default)] p-4 text-[11px] leading-relaxed text-[var(--color-text-primary)] max-h-72 overflow-y-auto scrollbar-thin\">\n {JSON.stringify(p.manifest, null, 2)}\n </pre>\n </div>\n )}\n </div>\n )}\n\n {/* Rejection / Delist form */}\n {rejectingId === p.id && (\n <div\n className={`border-t border-[var(--color-border-seam)] px-5 py-4 ${p.status === 'PUBLISHED' ? 'bg-[var(--color-surface-warning-muted)]/30' : 'bg-[var(--color-surface-danger-muted)]/30'}`}\n >\n {p.status === 'PUBLISHED' ? (\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-warning)]\">\n Delist — this removes the product from the store for new users. Existing\n installs keep working. Reason is optional.\n </p>\n ) : (\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-danger)]\">\n Reject — provide a reason (the publisher will see this)\n </p>\n )}\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label={p.status === 'PUBLISHED' ? 'Delist reason' : 'Rejection reason'}\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={3}\n required={p.status !== 'PUBLISHED'}\n aria-required={p.status !== 'PUBLISHED'}\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={\n p.status === 'PUBLISHED'\n ? 'Optional — reason for delisting…'\n : \"Be specific — e.g. 'Manifest is missing the identity.version field' or 'ZIP file is empty'…\"\n }\n />\n <div className=\"mt-2 flex items-center 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.5 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\n >\n Cancel\n </button>\n {p.status === 'PUBLISHED' ? (\n <button\n type=\"button\"\n onClick={() => void delist(p.id)}\n disabled={actingId === p.id}\n 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\"\n >\n Confirm delist\n </button>\n ) : (\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.5 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 )}\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </main>\n\n {/* Test in workspace modal */}\n {testingProduct && (\n <InstallAppModal\n isOpen={true}\n onClose={() => setTestingProduct(null)}\n app={{\n id: testingProduct.id,\n name: testingProduct.name,\n slug: testingProduct.slug,\n icon: testingProduct.icon ?? undefined,\n type: testingProduct.type,\n permissions: testingProduct.requiredPermissions?.length\n ? testingProduct.requiredPermissions\n : undefined,\n }}\n purchaseState=\"free\"\n onInstallSuccess={() => {\n setTestingProduct(null);\n }}\n onInstallError={() => {}}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AdminSubmissionsQueuePage.js","names":[],"sources":["../../src/pages/AdminSubmissionsQueuePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n Check,\n X,\n RefreshCw,\n PackageCheck,\n ChevronDown,\n ChevronUp,\n ExternalLink,\n FileJson,\n ShieldCheck,\n FlaskConical,\n Download,\n Clock,\n Tag,\n User,\n Hash,\n GitBranch,\n} from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router';\nimport { useStore } from '../providers/StoreProvider';\nimport { formatPriceForModel } from '../utils';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport Markdown from 'react-markdown';\nimport { InstallAppModal } from '../components/InstallAppModal';\n\ntype PendingProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature: string;\n status: string;\n pricingModel: string;\n price: number;\n currency: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n category?: string | null;\n icon?: string | null;\n productId?: string | null;\n zipFileUrl?: string | null;\n manifest?: Record<string, unknown> | null;\n requiredPermissions?: string[] | null;\n publisher?: { id: string; name: string; email: string } | null;\n};\n\ntype ProductStatus = 'PENDING_REVIEW' | 'PUBLISHED' | 'REJECTED' | 'ALL' | 'VERSION_UPDATES';\n\ntype PendingVersion = {\n id: string;\n version: string;\n status: string;\n releaseNotes?: string | null;\n packageUrl?: string | null;\n packageSize?: number | null;\n isLatest?: boolean | null;\n reviewNotes?: string | null;\n manifest?: Record<string, unknown> | null;\n createdAt: string;\n};\n\ntype VersionUpdateApp = {\n id: string;\n name: string;\n slug: string;\n iconUrl?: string | null;\n shortDescription?: string | null;\n description?: string | null;\n versions: { edges: Array<{ node: PendingVersion }> };\n};\n\nconst PENDING_PRODUCTS_QUERY = /* GraphQL */ `\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`;\n\nconst ALL_PRODUCTS_QUERY = /* GraphQL */ `\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`;\n\nconst APPROVE_PRODUCT_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!, $notes: String) {\n approveProduct(id: $id, notes: $notes) {\n id\n status\n publishedAt\n }\n }\n`;\n\nconst REJECT_PRODUCT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n rejectedReason\n }\n }\n`;\n\nconst DELIST_PRODUCT_MUTATION = /* GraphQL */ `\n mutation DelistProduct($id: ID!, $reason: String) {\n delistProduct(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst APP_PACKAGE_URL_QUERY = /* GraphQL */ `\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`;\n\nconst REFRESH_PACKAGE_URL_MUTATION = /* GraphQL */ `\n mutation RefreshApplicationVersionPackageUrl($id: ID!) {\n refreshApplicationVersionPackageUrl(id: $id)\n }\n`;\n\nconst VERSION_UPDATES_QUERY = /* GraphQL */ `\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`;\n\nconst APPROVE_VERSION_MUTATION = /* GraphQL */ `\n mutation AdminApproveApplicationVersion($id: ID!, $notes: String) {\n approveApplicationVersion(id: $id, notes: $notes) {\n id\n status\n }\n }\n`;\n\nconst REJECT_VERSION_MUTATION = /* GraphQL */ `\n mutation AdminRejectApplicationVersion($id: ID!, $reason: String!) {\n rejectApplicationVersion(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\nconst STATUS_FILTERS: Array<{ label: string; value: ProductStatus }> = [\n { label: 'Pending review', value: 'PENDING_REVIEW' },\n { label: 'Published', value: 'PUBLISHED' },\n { label: 'Rejected', value: 'REJECTED' },\n { label: 'Version Updates', value: 'VERSION_UPDATES' },\n { label: 'All', value: 'ALL' },\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\nconst STATUS_COLORS: Record<string, string> = {\n PENDING_REVIEW:\n 'bg-[var(--color-status-warning-bg-subtle)] text-[var(--color-status-warning-text)]',\n PUBLISHED: 'bg-[var(--color-status-success-bg-subtle)] text-[var(--color-status-success-text)]',\n REJECTED: 'bg-[var(--color-status-error-bg-subtle)] text-[var(--color-status-error-text)]',\n};\n\nconst PACKAGE_TYPES = new Set(['NODE', 'WORKFLOW', 'VIBEMODULE', 'PARSER', 'DATASINK', 'CONSOLE']);\n\nfunction typeColor(type: string) {\n return TYPE_COLORS[type] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]';\n}\n\nfunction statusColor(status: string) {\n return (\n STATUS_COLORS[status] ?? 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)]'\n );\n}\n\nfunction formatDate(iso?: string | null) {\n if (!iso) return '—';\n return new Date(iso).toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport const AdminSubmissionsQueuePage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n const [products, setProducts] = useState<PendingProduct[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [actingId, setActingId] = useState<string | null>(null);\n const [statusFilter, setStatusFilter] = useState<ProductStatus>('PENDING_REVIEW');\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [viewingId, setViewingId] = useState<string | null>(null);\n const [testingProduct, setTestingProduct] = useState<PendingProduct | null>(null);\n const [versionApps, setVersionApps] = useState<VersionUpdateApp[]>([]);\n const [versionUpdateCount, setVersionUpdateCount] = useState(0);\n const [versionActingId, setVersionActingId] = useState<string | null>(null);\n const [versionRejectingId, setVersionRejectingId] = useState<string | null>(null);\n const [versionRejectReason, setVersionRejectReason] = useState('');\n const [versionRejectAppId, setVersionRejectAppId] = useState<string | null>(null);\n // productId → { versionId, packageUrl } for legacy submissions where zipFileUrl was not populated\n const [appVersionInfo, setAppVersionInfo] = useState<\n Record<string, { versionId: string; packageUrl: string }>\n >({});\n const [downloadingVersionId, setDownloadingVersionId] = useState<string | null>(null);\n\n const loadVersionApps = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await graphqlFetch<{\n applicationsWithPendingVersions: {\n totalCount: number;\n edges: Array<{ node: VersionUpdateApp }>;\n };\n }>({\n gateway: 'global',\n query: VERSION_UPDATES_QUERY,\n variables: { limit: 50, offset: 0 },\n authToken: authToken || undefined,\n });\n if (result.errors?.length)\n throw new Error(result.errors[0]?.message ?? 'Failed to load version updates');\n const edges = result.data?.applicationsWithPendingVersions.edges ?? [];\n setVersionApps(edges.map((e) => e.node));\n setVersionUpdateCount(result.data?.applicationsWithPendingVersions.totalCount ?? 0);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load version updates');\n } finally {\n setLoading(false);\n }\n }, [authToken]);\n\n const approveVersion = async (versionId: string, appId: string, notes: string) => {\n setVersionActingId(versionId);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_VERSION_MUTATION,\n variables: { id: versionId, notes: notes || null },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) throw new Error(result.errors[0]?.message ?? 'Approval failed');\n await loadVersionApps();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setVersionActingId(null);\n }\n };\n\n const rejectVersion = async (versionId: string, appId: string, reason: string) => {\n if (!reason.trim()) {\n setError('Rejection reason is required');\n return;\n }\n setVersionActingId(versionId);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_VERSION_MUTATION,\n variables: { id: versionId, reason: reason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n setVersionRejectingId(null);\n setVersionRejectReason('');\n setVersionRejectAppId(null);\n await loadVersionApps();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setVersionActingId(null);\n }\n };\n\n const loadProducts = useCallback(\n async (filter: ProductStatus) => {\n if (filter === 'VERSION_UPDATES') {\n await loadVersionApps();\n return;\n }\n setLoading(true);\n setError(null);\n try {\n if (filter === 'PENDING_REVIEW') {\n const result = await graphqlFetch<{\n pendingReviewProducts: { totalCount: number; products: PendingProduct[] };\n }>({\n gateway: 'global',\n query: PENDING_PRODUCTS_QUERY,\n variables: { limit: 100, offset: 0 },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load pending products');\n }\n setProducts(result.data?.pendingReviewProducts.products ?? []);\n setTotalCount(result.data?.pendingReviewProducts.totalCount ?? 0);\n } else {\n const result = await graphqlFetch<{ products: PendingProduct[] }>({\n gateway: 'global',\n query: ALL_PRODUCTS_QUERY,\n variables: {\n limit: 100,\n offset: 0,\n ...(filter !== 'ALL' && { status: filter }),\n },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load products');\n }\n const list = result.data?.products ?? [];\n setProducts(list);\n setTotalCount(list.length);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setLoading(false);\n }\n },\n [authToken, loadVersionApps]\n );\n\n useEffect(() => {\n void loadProducts(statusFilter);\n }, [loadProducts, statusFilter]);\n\n // Keep version update count fresh for the badge even when not on that tab\n useEffect(() => {\n void loadVersionApps();\n }, [loadVersionApps]);\n\n // For PACKAGE_TYPES products whose zipFileUrl was not populated at submission time,\n // fetch the latest version ID + package URL directly from the devportal.\n useEffect(() => {\n const targets = products.filter(\n (p) => PACKAGE_TYPES.has(p.type) && p.productId && !p.zipFileUrl\n );\n if (targets.length === 0) return;\n void (async () => {\n const results = await Promise.allSettled(\n targets.map((p) =>\n graphqlFetch<{\n application: {\n id: string;\n versions: { edges: Array<{ node: { id: string; packageUrl?: string | null } }> };\n } | null;\n }>({\n gateway: 'global',\n query: APP_PACKAGE_URL_QUERY,\n variables: { id: p.productId },\n authToken: authToken || undefined,\n })\n )\n );\n setAppVersionInfo((prev) => {\n const next = { ...prev };\n results.forEach((r, i) => {\n if (r.status === 'fulfilled') {\n const node = r.value.data?.application?.versions?.edges?.[0]?.node;\n const productId = targets[i].productId;\n if (node?.id && node.packageUrl && productId) {\n next[productId] = { versionId: node.id, packageUrl: node.packageUrl };\n }\n }\n });\n return next;\n });\n })();\n }, [products, authToken]);\n\n const downloadPackage = async (versionId: string) => {\n setDownloadingVersionId(versionId);\n try {\n const result = await graphqlFetch<{ refreshApplicationVersionPackageUrl: string | null }>({\n gateway: 'global',\n query: REFRESH_PACKAGE_URL_MUTATION,\n variables: { id: versionId },\n authToken: authToken || undefined,\n });\n const freshUrl = result.data?.refreshApplicationVersionPackageUrl;\n if (freshUrl) {\n window.open(freshUrl, '_blank', 'noreferrer');\n } else {\n setError('Could not generate a download URL. S3 credentials may not be configured.');\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to refresh download URL');\n } finally {\n setDownloadingVersionId(null);\n }\n };\n\n const approve = async (id: string) => {\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_PRODUCT_MUTATION,\n variables: { id, notes: null },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await loadProducts(statusFilter);\n } catch (err) {\n setError(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()) {\n setError('Rejection reason is required');\n return;\n }\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_PRODUCT_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 loadProducts(statusFilter);\n } catch (err) {\n setError(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 setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: DELIST_PRODUCT_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 loadProducts(statusFilter);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Delist failed');\n } finally {\n setActingId(null);\n }\n };\n\n const pendingCount = products.filter((p) => p.status === 'PENDING_REVIEW').length;\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-5xl px-6 py-4\">\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/dashboard`)}\n className=\"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-sm text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n <ArrowLeft className=\"size-4 shrink-0\" />\n Back\n </button>\n <div className=\"h-5 w-px bg-[var(--color-border-seam)]\" />\n <PackageCheck className=\"size-5 shrink-0 text-[var(--color-text-primary)]\" />\n <h1 className=\"text-lg font-semibold text-[var(--color-text-primary)] truncate\">\n Submission Queue\n </h1>\n {totalCount > 0 && (\n <span className=\"shrink-0 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 {totalCount}\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => void loadProducts(statusFilter)}\n className=\"shrink-0 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 disabled={loading}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n\n {/* Status filter pills */}\n <div className=\"mt-3 flex gap-1.5 flex-wrap\">\n {STATUS_FILTERS.map((f) => (\n <button\n key={f.value}\n type=\"button\"\n onClick={() => setStatusFilter(f.value)}\n className={`rounded-full px-3.5 py-1 text-xs font-medium transition-colors ${\n statusFilter === f.value\n ? 'bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]'\n : 'bg-[var(--color-surface-muted)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]'\n }`}\n >\n {f.label}\n {f.value === 'PENDING_REVIEW' &&\n pendingCount > 0 &&\n statusFilter !== 'PENDING_REVIEW' && (\n <span className=\"ml-1.5 rounded-full bg-[var(--color-status-warning-text)] px-1.5 py-0.5 text-[10px] text-white\">\n {pendingCount}\n </span>\n )}\n {f.value === 'VERSION_UPDATES' &&\n versionUpdateCount > 0 &&\n statusFilter !== 'VERSION_UPDATES' && (\n <span className=\"ml-1.5 rounded-full bg-[var(--color-status-info-text)] px-1.5 py-0.5 text-[10px] text-white\">\n {versionUpdateCount}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-5xl space-y-4 p-6\">\n <PagePurpose className=\"mb-2\">\n Every publisher submission lands here for your review. Inspect the manifest, permissions,\n and package details — then approve to publish or reject with a clear reason the developer\n will see.\n </PagePurpose>\n\n {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 {error}\n </div>\n )}\n\n {statusFilter === 'VERSION_UPDATES' ? (\n loading && versionApps.length === 0 ? (\n <div className=\"flex items-center justify-center py-24\">\n <RefreshCw className=\"size-6 animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : versionApps.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No pending version updates\"\n description=\"All published apps are up to date. When a developer submits a new version, it will appear here for your review.\"\n />\n ) : (\n <ul className=\"space-y-4\">\n {versionApps.map((app) => {\n const pendingVersions = app.versions.edges\n .map((e) => e.node)\n .filter((v) => v.status === 'pending_review');\n if (pendingVersions.length === 0) return null;\n return (\n <li\n key={app.id}\n className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)]\"\n >\n {/* App header */}\n <div className=\"flex items-center gap-4 p-5 border-b border-[var(--color-border-seam)]\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt=\"\"\n className=\"size-12 shrink-0 rounded-xl object-cover shadow-sm\"\n />\n ) : (\n <div className=\"flex size-12 shrink-0 items-center justify-center rounded-xl bg-[var(--color-surface-muted)] text-sm font-bold text-[var(--color-text-muted)] uppercase\">\n {app.name.slice(0, 2)}\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n {app.name}\n </h3>\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-status-info-bg-subtle)] px-2 py-0.5 text-[11px] font-semibold text-[var(--color-status-info-text)]\">\n <GitBranch className=\"size-3\" />\n {pendingVersions.length} version\n {pendingVersions.length !== 1 ? 's' : ''} pending\n </span>\n </div>\n {(app.shortDescription ?? app.description) && (\n <p className=\"mt-0.5 text-sm text-[var(--color-text-secondary)] line-clamp-1\">\n {app.shortDescription ?? app.description}\n </p>\n )}\n </div>\n <a\n href={`/devportal/apps/${app.id}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"shrink-0 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\"\n >\n <ExternalLink className=\"size-3.5\" />\n Devportal\n </a>\n </div>\n\n {/* Pending versions */}\n <div className=\"divide-y divide-[var(--color-border-seam)]\">\n {pendingVersions.map((ver) => (\n <div key={ver.id} className=\"p-5 space-y-3\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <GitBranch className=\"size-4 text-[var(--color-text-secondary)] shrink-0\" />\n <span className=\"font-mono font-semibold text-sm text-[var(--color-text-primary)]\">\n {ver.version}\n </span>\n {ver.isLatest && (\n <span className=\"rounded-full bg-[var(--color-surface-muted)] px-2 py-0.5 text-[10px] text-[var(--color-text-secondary)]\">\n Latest\n </span>\n )}\n <span className=\"rounded-full bg-[var(--color-status-warning-bg-subtle)] px-2 py-0.5 text-[10px] font-medium text-[var(--color-status-warning-text)] uppercase\">\n New Version\n </span>\n <span className=\"ml-auto flex items-center gap-1 text-xs text-[var(--color-text-secondary)]\">\n <Clock className=\"size-3\" />\n {formatDate(ver.createdAt)}\n </span>\n </div>\n\n {ver.releaseNotes && (\n <div className=\"text-sm text-[var(--color-text-secondary)]\">\n <Markdown>{ver.releaseNotes}</Markdown>\n </div>\n )}\n\n <div className=\"flex flex-wrap gap-2\">\n {ver.packageUrl && (\n <button\n type=\"button\"\n onClick={() => void downloadPackage(ver.id)}\n disabled={downloadingVersionId === ver.id}\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)] transition-colors disabled:opacity-50\"\n >\n {downloadingVersionId === ver.id ? (\n <RefreshCw className=\"size-3.5 animate-spin\" />\n ) : (\n <Download className=\"size-3.5\" />\n )}\n Download Package\n {ver.packageSize != null && (\n <span className=\"text-[var(--color-text-muted)]\">\n ({ver.packageSize} bytes)\n </span>\n )}\n </button>\n )}\n {ver.manifest && (\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === ver.id ? null : ver.id)}\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)] transition-colors\"\n >\n <FileJson className=\"size-3.5\" />\n {viewingId === ver.id ? 'Hide manifest' : 'View manifest'}\n {viewingId === ver.id ? (\n <ChevronUp className=\"size-3\" />\n ) : (\n <ChevronDown className=\"size-3\" />\n )}\n </button>\n )}\n </div>\n\n {viewingId === ver.id && ver.manifest && (\n <pre className=\"overflow-x-auto rounded-lg border border-[var(--color-border-seam)] bg-[var(--color-surface-default)] p-4 text-[11px] leading-relaxed text-[var(--color-text-primary)] max-h-48 overflow-y-auto\">\n {JSON.stringify(ver.manifest, null, 2)}\n </pre>\n )}\n\n {/* Reject reason input */}\n {versionRejectingId === ver.id && versionRejectAppId === app.id && (\n <div className=\"rounded-lg border border-[var(--color-border-danger)] bg-[var(--color-surface-danger-muted)]/30 p-3 space-y-2\">\n <p className=\"text-xs font-semibold text-[var(--color-text-danger)]\">\n Rejection reason (the developer will see this)\n </p>\n <textarea\n value={versionRejectReason}\n onChange={(e) => setVersionRejectReason(e.target.value)}\n rows={2}\n 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\"\n placeholder=\"Be specific — what needs to change before this version can be approved…\"\n />\n <div className=\"flex gap-2 justify-end\">\n <button\n type=\"button\"\n onClick={() => {\n setVersionRejectingId(null);\n setVersionRejectReason('');\n setVersionRejectAppId(null);\n }}\n 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\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() =>\n void rejectVersion(ver.id, app.id, versionRejectReason)\n }\n disabled={\n !versionRejectReason.trim() || versionActingId === ver.id\n }\n 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\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => void approveVersion(ver.id, app.id, '')}\n disabled={versionActingId === ver.id}\n 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\"\n >\n {versionActingId === ver.id ? (\n <RefreshCw className=\"size-3.5 animate-spin\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n Approve version\n </button>\n {versionRejectingId !== ver.id && (\n <button\n type=\"button\"\n onClick={() => {\n setVersionRejectingId(ver.id);\n setVersionRejectReason('');\n setVersionRejectAppId(app.id);\n }}\n disabled={versionActingId === ver.id}\n 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\"\n >\n <X className=\"size-3.5\" />\n Reject version\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n </li>\n );\n })}\n </ul>\n )\n ) : loading && products.length === 0 ? (\n <div className=\"flex items-center justify-center py-24\">\n <RefreshCw className=\"size-6 animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : products.length === 0 ? (\n <IllustratedEmptyState\n illustration={statusFilter === 'PENDING_REVIEW' ? 'empty-notifications' : 'empty-data'}\n title={\n statusFilter === 'PENDING_REVIEW' ? 'Queue is clear' : 'No products match this filter'\n }\n description={\n statusFilter === 'PENDING_REVIEW'\n ? 'No submissions are awaiting review. New submissions will appear here as publishers send them in.'\n : 'Nothing matches the current filter. Try switching to a different status above.'\n }\n />\n ) : (\n <ul className=\"space-y-3\">\n {products.map((p) => (\n <li\n key={p.id}\n className=\"overflow-hidden rounded-xl border border-[var(--color-border-seam)] bg-[var(--color-surface-raised)] shadow-[var(--shadow-elevation-1)] transition-[box-shadow,border-color] hover:border-[var(--color-border-strong)] hover:shadow-[var(--shadow-elevation-2)]\"\n >\n {/* Card body */}\n <div className=\"flex items-start gap-4 p-5\">\n {/* App icon */}\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-14 shrink-0 rounded-xl object-cover shadow-sm\"\n />\n ) : (\n <div className=\"flex size-14 shrink-0 items-center justify-center rounded-xl bg-[var(--color-surface-muted)] text-sm font-bold text-[var(--color-text-muted)] uppercase\">\n {p.type.slice(0, 2)}\n </div>\n )}\n\n {/* Main info */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n {p.name}\n </h3>\n <span\n className={`rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide ${typeColor(p.type)}`}\n >\n {p.type}\n </span>\n <span\n className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${statusColor(p.status)}`}\n >\n {p.status.replace('_', ' ')}\n </span>\n </div>\n\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-[var(--color-text-secondary)]\">\n <span className=\"flex items-center gap-1\">\n <Hash className=\"size-3\" />\n <span className=\"font-mono\">{p.slug}</span>\n </span>\n {p.publisher && (\n <span className=\"flex items-center gap-1\">\n <User className=\"size-3\" />\n {p.publisher.name}\n {p.publisher.email && (\n <span className=\"text-[var(--color-text-muted)]\">\n ({p.publisher.email})\n </span>\n )}\n </span>\n )}\n {p.submittedAt && (\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n {formatDate(p.submittedAt)}\n </span>\n )}\n <span className=\"flex items-center gap-1\">\n <Tag className=\"size-3\" />\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}\n </span>\n </div>\n\n {p.description && (\n <p className=\"mt-2 line-clamp-2 text-sm text-[var(--color-text-secondary)]\">\n {p.description}\n </p>\n )}\n </div>\n </div>\n\n {/* Action bar */}\n <div className=\"flex flex-wrap items-center justify-between gap-2 border-t border-[var(--color-border-seam)] bg-[var(--color-surface-muted)] px-5 py-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {/* View in devportal */}\n {p.productId && (\n <a\n href={`/devportal/apps/${p.productId}`}\n target=\"_blank\"\n rel=\"noreferrer\"\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 <ExternalLink className=\"size-3.5\" />\n Devportal\n </a>\n )}\n\n {/* Package download for NODE/WORKFLOW. Always calls the refresh mutation\n to get a fresh presigned URL (stored URLs expire in 1h). */}\n {PACKAGE_TYPES.has(p.type) &&\n (() => {\n // Resolve the version ID to use with the refresh mutation\n const versionId = p.productId\n ? (appVersionInfo[p.productId]?.versionId ??\n versionApps.find((va) => va.id === p.productId)?.versions?.edges?.[0]\n ?.node?.id)\n : undefined;\n // If we have no version ID yet, check if we at least have zipFileUrl\n // (new submissions populated by the backend fix)\n if (!versionId && !p.zipFileUrl) return null;\n const isLoading = downloadingVersionId === versionId;\n if (versionId) {\n return (\n <button\n type=\"button\"\n onClick={() => void downloadPackage(versionId)}\n disabled={isLoading}\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 disabled:opacity-50\"\n >\n {isLoading ? (\n <RefreshCw className=\"size-3.5 animate-spin\" />\n ) : (\n <Download className=\"size-3.5\" />\n )}\n Download Package\n </button>\n );\n }\n // Fallback: direct link when we have zipFileUrl but no versionId yet\n return (\n <a\n href={p.zipFileUrl!}\n download\n target=\"_blank\"\n rel=\"noreferrer\"\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 <Download className=\"size-3.5\" />\n Download Package\n </a>\n );\n })()}\n\n {/* Test in workspace for NODE/WORKFLOW */}\n {PACKAGE_TYPES.has(p.type) && (\n <button\n type=\"button\"\n onClick={() => setTestingProduct(p)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-[var(--color-border-accent)] bg-[var(--color-surface-default)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-accent)] hover:bg-[var(--color-surface-accent-muted)] transition-colors\"\n >\n <FlaskConical className=\"size-3.5\" />\n Test in workspace\n </button>\n )}\n\n {/* Show manifest / details toggle */}\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\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)] transition-colors\"\n >\n <FileJson className=\"size-3.5\" />\n {viewingId === p.id ? 'Hide details' : 'Inspect details'}\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3\" />\n ) : (\n <ChevronDown className=\"size-3\" />\n )}\n </button>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {p.status !== 'PUBLISHED' && (\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n 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\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n )}\n {p.status === 'PENDING_REVIEW' && (\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n }}\n disabled={actingId === p.id}\n 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\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n )}\n {p.status === 'PUBLISHED' && (\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n }}\n disabled={actingId === p.id}\n 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\"\n >\n <X className=\"size-3.5\" />\n Delist\n </button>\n )}\n </div>\n </div>\n\n {/* Details panel */}\n {viewingId === p.id && (\n <div className=\"border-t border-[var(--color-border-seam)] bg-[var(--color-surface-muted)] p-5 space-y-5\">\n {/* Metadata grid */}\n <div 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)]\">\n Store product ID\n </dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)] break-all\">\n {p.id}\n </dd>\n </div>\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)]\">\n Type / Nature\n </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)]\">{p.category}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">Pricing</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)]\">({p.pricingModel})</span>\n </dd>\n </div>\n {p.productId && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Devportal app ID\n </dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)] break-all\">\n {p.productId}\n </dd>\n </div>\n )}\n {p.submittedBy && (\n <div>\n <dt className=\"font-medium text-[var(--color-text-secondary)]\">\n Submitted by (actor)\n </dt>\n <dd className=\"mt-0.5 font-mono text-[var(--color-text-primary)] break-all\">\n {p.submittedBy}\n </dd>\n </div>\n )}\n </div>\n\n {/* Required permissions */}\n {p.requiredPermissions && p.requiredPermissions.length > 0 && (\n <div>\n <h4 className=\"mb-2 flex items-center gap-1.5 text-xs font-semibold text-[var(--color-text-secondary)] uppercase tracking-wide\">\n <ShieldCheck className=\"size-3.5 text-[var(--color-status-success-text)]\" />\n Required Permissions\n </h4>\n <div className=\"flex flex-wrap gap-1.5\">\n {p.requiredPermissions.map((perm) => (\n <span\n key={perm}\n className=\"inline-flex items-center gap-1 rounded-md bg-[var(--color-surface-default)] border border-[var(--color-border-seam)] px-2 py-1 text-xs font-mono text-[var(--color-text-primary)]\"\n >\n {perm}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Manifest JSON */}\n {p.manifest && (\n <div>\n <h4 className=\"mb-2 flex items-center gap-1.5 text-xs font-semibold text-[var(--color-text-secondary)] uppercase tracking-wide\">\n <FileJson className=\"size-3.5\" />\n Manifest\n </h4>\n <pre className=\"overflow-x-auto rounded-lg border border-[var(--color-border-seam)] bg-[var(--color-surface-default)] p-4 text-[11px] leading-relaxed text-[var(--color-text-primary)] max-h-72 overflow-y-auto scrollbar-thin\">\n {JSON.stringify(p.manifest, null, 2)}\n </pre>\n </div>\n )}\n </div>\n )}\n\n {/* Rejection / Delist form */}\n {rejectingId === p.id && (\n <div\n className={`border-t border-[var(--color-border-seam)] px-5 py-4 ${p.status === 'PUBLISHED' ? 'bg-[var(--color-surface-warning-muted)]/30' : 'bg-[var(--color-surface-danger-muted)]/30'}`}\n >\n {p.status === 'PUBLISHED' ? (\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-warning)]\">\n Delist — this removes the product from the store for new users. Existing\n installs keep working. Reason is optional.\n </p>\n ) : (\n <p className=\"mb-2 text-xs font-semibold text-[var(--color-text-danger)]\">\n Reject — provide a reason (the publisher will see this)\n </p>\n )}\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label={p.status === 'PUBLISHED' ? 'Delist reason' : 'Rejection reason'}\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={3}\n required={p.status !== 'PUBLISHED'}\n aria-required={p.status !== 'PUBLISHED'}\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={\n p.status === 'PUBLISHED'\n ? 'Optional — reason for delisting…'\n : \"Be specific — e.g. 'Manifest is missing the identity.version field' or 'ZIP file is empty'…\"\n }\n />\n <div className=\"mt-2 flex items-center 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.5 text-xs font-medium text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors\"\n >\n Cancel\n </button>\n {p.status === 'PUBLISHED' ? (\n <button\n type=\"button\"\n onClick={() => void delist(p.id)}\n disabled={actingId === p.id}\n 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\"\n >\n Confirm delist\n </button>\n ) : (\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.5 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 )}\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </main>\n\n {/* Test in workspace modal */}\n {testingProduct && (\n <InstallAppModal\n isOpen={true}\n onClose={() => setTestingProduct(null)}\n app={{\n id: testingProduct.id,\n name: testingProduct.name,\n slug: testingProduct.slug,\n icon: testingProduct.icon ?? undefined,\n type: testingProduct.type,\n permissions: testingProduct.requiredPermissions?.length\n ? testingProduct.requiredPermissions\n : undefined,\n }}\n purchaseState=\"free\"\n onInstallSuccess={() => {\n setTestingProduct(null);\n }}\n onInstallError={() => {}}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { useInstallation as t } from "../hooks/useInstallations.js";
3
3
  import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
4
  import { ArrowLeft as r, ExternalLink as i, Info as a, Settings as o } from "lucide-react";
5
5
  import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
6
- import { useNavigate as u, useParams as d } from "react-router-dom";
6
+ import { useNavigate as u, useParams as d } from "react-router";
7
7
  import { EmphasisPanel as f, IllustratedEmptyState as p, NextSteps as m, PagePurpose as h, ShowMore as g } from "@burdenoff/fe-libs/ui";
8
8
  //#region src/pages/AppDetailPage.tsx
9
9
  var _ = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"AppDetailPage.js","names":[],"sources":["../../src/pages/AppDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { ArrowLeft, Settings, ExternalLink, Info } from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { useInstallation } from '../hooks/useInstallations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n EmphasisPanel,\n ShowMore,\n PagePurpose,\n NextSteps,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nexport const AppDetailPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { installationId } = useParams<{ installationId: string }>();\n const { installation, loading, error } = useInstallation(installationId);\n const { t } = useI18n();\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"mx-auto flex max-w-4xl items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.appDetail.title', { defaultValue: 'Installed App Details' })}\n </h1>\n <p className=\"text-sm text-text-muted\">\n {t('pages.appDetail.subtitle', {\n defaultValue: 'Inspect installation status and metadata',\n })}\n </p>\n </div>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-4xl\">\n <PagePurpose className=\"mb-4\">\n The full record of one app installed into a workspace — its status, version, who\n installed it and when, plus its runtime metadata. Come here to confirm an app is\n healthy, dig into a failed install, or jump to its configuration before troubleshooting.\n </PagePurpose>\n {loading ? (\n <p role=\"status\" className=\"text-sm text-text-muted\">\n Loading installation…\n </p>\n ) : error ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {error.message}\n </p>\n ) : !installation ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"Installation not found\"\n description=\"This installation may have been uninstalled or never existed. Head back to your installed apps to find another.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to installed apps\n </button>\n }\n />\n ) : (\n <>\n {(() => {\n const manifest = installation.manifest as Record<string, unknown> | null;\n const appType = manifest?.type as string | undefined;\n const isIntegration = appType === 'INTEGRATION' || appType === 'API_INTEGRATION';\n const installationId = manifest?.installationId as string | undefined;\n const appName =\n (manifest?.name as string | undefined) ??\n (manifest?.slug as string | undefined)\n ?.split('-')\n .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ') ??\n installation.productId;\n\n return (\n <>\n {isIntegration ? (\n <div className=\"mb-4 flex items-start gap-3 rounded-xl border border-border-default bg-bg-surface p-4\">\n <Info className=\"mt-0.5 size-5 shrink-0 text-text-muted\" />\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">\n Configuration is managed on the third-party's side\n </p>\n <p className=\"mt-1 text-text-muted\">\n <strong>{appName}</strong> is an external integration connected via\n OAuth. All settings (webhooks, scopes, data mapping) are configured in\n the third-party's dashboard — not here. To revoke access, use{' '}\n <strong>Uninstall</strong>.\n </p>\n {installationId && (\n <p className=\"mt-2 rounded-lg bg-bg-sunken px-3 py-2 font-mono text-xs text-text-muted\">\n Installation ID:{' '}\n <span className=\"select-all text-text-primary\">{installationId}</span>\n </p>\n )}\n </div>\n </div>\n ) : (\n <NextSteps\n className=\"mb-4\"\n storageKey=\"store-app-detail\"\n steps={[\n {\n id: 'configure',\n label: 'Manage configuration',\n description: \"Adjust this installation's settings for the workspace.\",\n icon: <Settings className=\"size-4\" />,\n onClick: () =>\n navigate(`${basePath}/installed/${installation.id}/settings`),\n },\n {\n id: 'view-product',\n label: 'Open the product page',\n description: 'See the latest version, changelog and reviews.',\n onClick: () =>\n navigate(`${basePath}/marketplace/product/${installation.productId}`),\n },\n {\n id: 'back',\n label: 'Back to installed apps',\n description: 'Return to the full list of apps in your workspaces.',\n onClick: () => navigate(`${basePath}/installed`),\n },\n ]}\n />\n )}\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{appName}</h2>\n <p className=\"mt-1 text-sm text-text-muted\">\n Workspace: {installation.workspaceName ?? installation.workspaceId}\n </p>\n {appType && (\n <span className=\"mt-1 inline-block rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {appType}\n </span>\n )}\n </div>\n <span className=\"inline-flex w-fit rounded-full bg-bg-sunken px-3 py-1 text-xs font-medium text-text-primary\">\n {installation.status}\n </span>\n </div>\n\n <div className=\"mt-4 grid gap-4 md:grid-cols-2\">\n <div className=\"rounded-lg bg-bg-sunken p-4 text-sm text-text-muted\">\n <p>\n <span className=\"font-medium text-text-primary\">Version:</span>{' '}\n {installation.version ?? 'Not provided'}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Installed at:</span>{' '}\n {new Date(installation.installedAt).toLocaleString()}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Updated at:</span>{' '}\n {new Date(installation.updatedAt).toLocaleString()}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Workspace ID:</span>{' '}\n {installation.workspaceId}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Organization ID:</span>{' '}\n {installation.organizationId ?? 'N/A'}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Installed by:</span>{' '}\n {installation.installedById}\n </p>\n </div>\n\n <div className=\"rounded-lg bg-bg-sunken p-4 text-sm text-text-muted\">\n <p className=\"font-medium text-text-primary\">Runtime metadata</p>\n <ShowMore collapsedHeight={120} label=\"metadata\" className=\"mt-3\">\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-lg border border-border-seam bg-bg-surface p-3 text-xs text-text-muted\">\n {JSON.stringify(\n {\n metadata: installation.metadata,\n context: installation.context,\n uninstalledAt: installation.uninstalledAt,\n uninstalledById: installation.uninstalledById,\n },\n null,\n 2\n )}\n </pre>\n </ShowMore>\n </div>\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-3\">\n {isIntegration ? (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed`)}\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Installed Apps\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/installed/${installation.id}/settings`)\n }\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Settings className=\"size-4\" />\n {t('pages.appDetail.manageConfig', {\n defaultValue: 'Manage Configuration',\n })}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/marketplace/product/${installation.productId}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <ExternalLink className=\"size-4\" />\n {t('pages.appDetail.viewProduct', { defaultValue: 'View Product' })}\n </button>\n </div>\n </EmphasisPanel>\n </>\n );\n })()}\n </>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AppDetailPage.js","names":[],"sources":["../../src/pages/AppDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport { ArrowLeft, Settings, ExternalLink, Info } from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { useInstallation } from '../hooks/useInstallations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n EmphasisPanel,\n ShowMore,\n PagePurpose,\n NextSteps,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nexport const AppDetailPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { installationId } = useParams<{ installationId: string }>();\n const { installation, loading, error } = useInstallation(installationId);\n const { t } = useI18n();\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"mx-auto flex max-w-4xl items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.appDetail.title', { defaultValue: 'Installed App Details' })}\n </h1>\n <p className=\"text-sm text-text-muted\">\n {t('pages.appDetail.subtitle', {\n defaultValue: 'Inspect installation status and metadata',\n })}\n </p>\n </div>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-4xl\">\n <PagePurpose className=\"mb-4\">\n The full record of one app installed into a workspace — its status, version, who\n installed it and when, plus its runtime metadata. Come here to confirm an app is\n healthy, dig into a failed install, or jump to its configuration before troubleshooting.\n </PagePurpose>\n {loading ? (\n <p role=\"status\" className=\"text-sm text-text-muted\">\n Loading installation…\n </p>\n ) : error ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {error.message}\n </p>\n ) : !installation ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"Installation not found\"\n description=\"This installation may have been uninstalled or never existed. Head back to your installed apps to find another.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to installed apps\n </button>\n }\n />\n ) : (\n <>\n {(() => {\n const manifest = installation.manifest as Record<string, unknown> | null;\n const appType = manifest?.type as string | undefined;\n const isIntegration = appType === 'INTEGRATION' || appType === 'API_INTEGRATION';\n const installationId = manifest?.installationId as string | undefined;\n const appName =\n (manifest?.name as string | undefined) ??\n (manifest?.slug as string | undefined)\n ?.split('-')\n .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ') ??\n installation.productId;\n\n return (\n <>\n {isIntegration ? (\n <div className=\"mb-4 flex items-start gap-3 rounded-xl border border-border-default bg-bg-surface p-4\">\n <Info className=\"mt-0.5 size-5 shrink-0 text-text-muted\" />\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">\n Configuration is managed on the third-party's side\n </p>\n <p className=\"mt-1 text-text-muted\">\n <strong>{appName}</strong> is an external integration connected via\n OAuth. All settings (webhooks, scopes, data mapping) are configured in\n the third-party's dashboard — not here. To revoke access, use{' '}\n <strong>Uninstall</strong>.\n </p>\n {installationId && (\n <p className=\"mt-2 rounded-lg bg-bg-sunken px-3 py-2 font-mono text-xs text-text-muted\">\n Installation ID:{' '}\n <span className=\"select-all text-text-primary\">{installationId}</span>\n </p>\n )}\n </div>\n </div>\n ) : (\n <NextSteps\n className=\"mb-4\"\n storageKey=\"store-app-detail\"\n steps={[\n {\n id: 'configure',\n label: 'Manage configuration',\n description: \"Adjust this installation's settings for the workspace.\",\n icon: <Settings className=\"size-4\" />,\n onClick: () =>\n navigate(`${basePath}/installed/${installation.id}/settings`),\n },\n {\n id: 'view-product',\n label: 'Open the product page',\n description: 'See the latest version, changelog and reviews.',\n onClick: () =>\n navigate(`${basePath}/marketplace/product/${installation.productId}`),\n },\n {\n id: 'back',\n label: 'Back to installed apps',\n description: 'Return to the full list of apps in your workspaces.',\n onClick: () => navigate(`${basePath}/installed`),\n },\n ]}\n />\n )}\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{appName}</h2>\n <p className=\"mt-1 text-sm text-text-muted\">\n Workspace: {installation.workspaceName ?? installation.workspaceId}\n </p>\n {appType && (\n <span className=\"mt-1 inline-block rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {appType}\n </span>\n )}\n </div>\n <span className=\"inline-flex w-fit rounded-full bg-bg-sunken px-3 py-1 text-xs font-medium text-text-primary\">\n {installation.status}\n </span>\n </div>\n\n <div className=\"mt-4 grid gap-4 md:grid-cols-2\">\n <div className=\"rounded-lg bg-bg-sunken p-4 text-sm text-text-muted\">\n <p>\n <span className=\"font-medium text-text-primary\">Version:</span>{' '}\n {installation.version ?? 'Not provided'}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Installed at:</span>{' '}\n {new Date(installation.installedAt).toLocaleString()}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Updated at:</span>{' '}\n {new Date(installation.updatedAt).toLocaleString()}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Workspace ID:</span>{' '}\n {installation.workspaceId}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Organization ID:</span>{' '}\n {installation.organizationId ?? 'N/A'}\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Installed by:</span>{' '}\n {installation.installedById}\n </p>\n </div>\n\n <div className=\"rounded-lg bg-bg-sunken p-4 text-sm text-text-muted\">\n <p className=\"font-medium text-text-primary\">Runtime metadata</p>\n <ShowMore collapsedHeight={120} label=\"metadata\" className=\"mt-3\">\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-lg border border-border-seam bg-bg-surface p-3 text-xs text-text-muted\">\n {JSON.stringify(\n {\n metadata: installation.metadata,\n context: installation.context,\n uninstalledAt: installation.uninstalledAt,\n uninstalledById: installation.uninstalledById,\n },\n null,\n 2\n )}\n </pre>\n </ShowMore>\n </div>\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-3\">\n {isIntegration ? (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed`)}\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Installed Apps\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/installed/${installation.id}/settings`)\n }\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Settings className=\"size-4\" />\n {t('pages.appDetail.manageConfig', {\n defaultValue: 'Manage Configuration',\n })}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/marketplace/product/${installation.productId}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <ExternalLink className=\"size-4\" />\n {t('pages.appDetail.viewProduct', { defaultValue: 'View Product' })}\n </button>\n </div>\n </EmphasisPanel>\n </>\n );\n })()}\n </>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;AAcA,IAAa,UAA0B;CACrC,IAAM,IAAW,GAAa,EACxB,EAAE,gBAAa,GAAU,EACzB,EAAE,sBAAmB,GAAuC,EAC5D,EAAE,iBAAc,YAAS,aAAU,EAAgB,EAAe,EAClE,EAAE,SAAM,GAAS;AAEvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GAAQ,WAAU;aAChB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAS,GAAG;KAC3B,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;KACzB,CAAA,EACT,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAE,yBAAyB,EAAE,cAAc,yBAAyB,CAAC;KACnE,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAE,4BAA4B,EAC7B,cAAc,4CACf,CAAC;KACA,CAAA,CACA,EAAA,CAAA,CACF;;GACC,CAAA,EAET,kBAAC,QAAD;GAAM,WAAU;aACd,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAa,WAAU;eAAO;KAIhB,CAAA,EACb,IACC,kBAAC,KAAD;KAAG,MAAK;KAAS,WAAU;eAA0B;KAEjD,CAAA,GACF,IACF,kBAAC,KAAD;KAAG,MAAK;KAAQ,WAAU;eACvB,EAAM;KACL,CAAA,GACD,IAgBH,kBAAA,GAAA,EAAA,iBACU;KACN,IAAM,IAAW,EAAa,UACxB,IAAU,GAAU,MACpB,IAAgB,MAAY,iBAAiB,MAAY,mBACzD,IAAiB,GAAU,gBAC3B,IACH,GAAU,SACV,GAAU,OACP,MAAM,IAAI,CACX,KAAK,MAAc,EAAE,OAAO,EAAE,CAAC,aAAa,GAAG,EAAE,MAAM,EAAE,CAAC,CAC1D,KAAK,IAAI,IACZ,EAAa;AAEf,YACE,kBAAA,GAAA,EAAA,UAAA,CACG,IACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,0CAA2C,CAAA,EAC3D,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAEzC,CAAA;QACJ,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACE,kBAAC,UAAD,EAAA,UAAS,GAAiB,CAAA;;UAEoC;UAC9D,kBAAC,UAAD,EAAA,UAAQ,aAAkB,CAAA;;UACxB;;QACH,KACC,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAAwF;UACrE;UACjB,kBAAC,QAAD;WAAM,WAAU;qBAAgC;WAAsB,CAAA;UACpE;;QAEF;SACF;UAEN,kBAAC,GAAD;MACE,WAAU;MACV,YAAW;MACX,OAAO;OACL;QACE,IAAI;QACJ,OAAO;QACP,aAAa;QACb,MAAM,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;QACrC,eACE,EAAS,GAAG,EAAS,aAAa,EAAa,GAAG,WAAW;QAChE;OACD;QACE,IAAI;QACJ,OAAO;QACP,aAAa;QACb,eACE,EAAS,GAAG,EAAS,uBAAuB,EAAa,YAAY;QACxE;OACD;QACE,IAAI;QACJ,OAAO;QACP,aAAa;QACb,eAAe,EAAS,GAAG,EAAS,YAAY;QACjD;OACF;MACD,CAAA,EAGJ,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,MAAD;SAAI,WAAU;mBAA2C;SAAa,CAAA;QACtE,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAA4C,eAC9B,EAAa,iBAAiB,EAAa,YACrD;;QACH,KACC,kBAAC,QAAD;SAAM,WAAU;mBACb;SACI,CAAA;QAEL,EAAA,CAAA,EACN,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAa;QACT,CAAA,CACH;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,KAAD,EAAA,UAAA;UACE,kBAAC,QAAD;WAAM,WAAU;qBAAgC;WAAe,CAAA;UAAC;UAC/D,EAAa,WAAW;UACvB,EAAA,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACE,kBAAC,QAAD;YAAM,WAAU;sBAAgC;YAAoB,CAAA;WAAC;WACpE,IAAI,KAAK,EAAa,YAAY,CAAC,gBAAgB;WAClD;;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACE,kBAAC,QAAD;YAAM,WAAU;sBAAgC;YAAkB,CAAA;WAAC;WAClE,IAAI,KAAK,EAAa,UAAU,CAAC,gBAAgB;WAChD;;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACE,kBAAC,QAAD;YAAM,WAAU;sBAAgC;YAAoB,CAAA;WAAC;WACpE,EAAa;WACZ;;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACE,kBAAC,QAAD;YAAM,WAAU;sBAAgC;YAAuB,CAAA;WAAC;WACvE,EAAa,kBAAkB;WAC9B;;SACJ,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACE,kBAAC,QAAD;YAAM,WAAU;sBAAgC;YAAoB,CAAA;WAAC;WACpE,EAAa;WACZ;;SACA;WAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAAoB,CAAA,EACjE,kBAAC,GAAD;SAAU,iBAAiB;SAAK,OAAM;SAAW,WAAU;mBACzD,kBAAC,OAAD;UAAK,WAAU;oBACZ,KAAK,UACJ;WACE,UAAU,EAAa;WACvB,SAAS,EAAa;WACtB,eAAe,EAAa;WAC5B,iBAAiB,EAAa;WAC/B,EACD,MACA,EACD;UACG,CAAA;SACG,CAAA,CACP;UACF;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,IACC,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAS,GAAG,EAAS,YAAY;QAChD,WAAU;kBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,yBAEzB;YAET,kBAAC,UAAD;QACE,MAAK;QACL,eACE,EAAS,GAAG,EAAS,aAAa,EAAa,GAAG,WAAW;QAE/D,WAAU;kBALZ,CAOE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA,EAC9B,EAAE,gCAAgC,EACjC,cAAc,wBACf,CAAC,CACK;WAEX,kBAAC,UAAD;QACE,MAAK;QACL,eACE,EAAS,GAAG,EAAS,uBAAuB,EAAa,YAAY;QAEvE,WAAU;kBALZ,CAOE,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,EAClC,EAAE,+BAA+B,EAAE,cAAc,gBAAgB,CAAC,CAC5D;UACL;;MACQ,EAAA,CAAA,CACf,EAAA,CAAA;QAEH,EACH,CAAA,GA1LH,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,GAAG,EAAS,YAAY;MAChD,WAAU;gBACX;MAEQ,CAAA;KAEX,CAAA,CA+KA;;GACD,CAAA,CACH"}
@@ -3,7 +3,7 @@ import { useInstallation as t } from "../hooks/useInstallations.js";
3
3
  import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
4
  import { ArrowLeft as r, Settings as i } from "lucide-react";
5
5
  import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
- import { useNavigate as s, useParams as c } from "react-router-dom";
6
+ import { useNavigate as s, useParams as c } from "react-router";
7
7
  import { EmphasisPanel as l, IllustratedEmptyState as u, PagePurpose as d, ShowMore as f } from "@burdenoff/fe-libs/ui";
8
8
  //#region src/pages/AppSettingsPage.tsx
9
9
  var p = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"AppSettingsPage.js","names":[],"sources":["../../src/pages/AppSettingsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { ArrowLeft, Settings } from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { useInstallation } from '../hooks/useInstallations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { EmphasisPanel, ShowMore, PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nexport const AppSettingsPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { installationId } = useParams<{ installationId: string }>();\n const { installation, loading, error } = useInstallation(installationId);\n const { t } = useI18n();\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"mx-auto flex max-w-4xl items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.appSettings.title', { defaultValue: 'App Settings' })}\n </h1>\n <p className=\"text-sm text-text-muted\">\n {t('pages.appSettings.subtitle', {\n defaultValue: 'Review current installation configuration',\n })}\n </p>\n </div>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-4xl\">\n <PagePurpose className=\"mb-4\">\n A read-only snapshot of how this app is configured in the workspace — its manifest,\n runtime metadata and context. Use it to verify what an installation is actually running\n (handy when an app behaves unexpectedly) before changing anything inside the product\n itself.\n </PagePurpose>\n {loading ? (\n <p role=\"status\" className=\"text-sm text-text-muted\">\n Loading settings…\n </p>\n ) : error ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {error.message}\n </p>\n ) : !installation ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"Installation not found\"\n description=\"There's no configuration to show — this installation may have been removed. Return to your installed apps to pick another.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to installed apps\n </button>\n }\n />\n ) : (\n <EmphasisPanel className=\"space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Settings className=\"size-5 text-text-primary\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('pages.appSettings.configSnapshot', {\n defaultValue: 'Configuration snapshot',\n })}\n </h2>\n </div>\n\n <p className=\"text-sm text-text-muted\">\n {t('pages.appSettings.configDescription', {\n defaultValue:\n 'Store-level configuration for this installation is read-only here.',\n })}\n </p>\n\n <ShowMore collapsedHeight={140} label=\"configuration details\">\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">Manifest</p>\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-lg border border-border-seam bg-bg-sunken p-3 text-xs text-text-muted\">\n {JSON.stringify(installation.manifest ?? {}, null, 2)}\n </pre>\n </div>\n\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">\n Metadata &amp; Context\n </p>\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-lg border border-border-seam bg-bg-sunken p-3 text-xs text-text-muted\">\n {JSON.stringify(\n {\n metadata: installation.metadata ?? {},\n context: installation.context ?? {},\n version: installation.version,\n status: installation.status,\n },\n null,\n 2\n )}\n </pre>\n </div>\n </div>\n </ShowMore>\n\n <div className=\"flex flex-wrap gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed/${installation.id}`)}\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {t('pages.appSettings.viewInstallation', { defaultValue: 'View Installation' })}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/marketplace/product/${installation.productId}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text hover:opacity-90\"\n >\n {t('pages.appDetail.viewProduct', { defaultValue: 'View Product' })}\n </button>\n </div>\n </EmphasisPanel>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AppSettingsPage.js","names":[],"sources":["../../src/pages/AppSettingsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport { ArrowLeft, Settings } from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { useInstallation } from '../hooks/useInstallations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { EmphasisPanel, ShowMore, PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nexport const AppSettingsPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { installationId } = useParams<{ installationId: string }>();\n const { installation, loading, error } = useInstallation(installationId);\n const { t } = useI18n();\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"mx-auto flex max-w-4xl items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.appSettings.title', { defaultValue: 'App Settings' })}\n </h1>\n <p className=\"text-sm text-text-muted\">\n {t('pages.appSettings.subtitle', {\n defaultValue: 'Review current installation configuration',\n })}\n </p>\n </div>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-4xl\">\n <PagePurpose className=\"mb-4\">\n A read-only snapshot of how this app is configured in the workspace — its manifest,\n runtime metadata and context. Use it to verify what an installation is actually running\n (handy when an app behaves unexpectedly) before changing anything inside the product\n itself.\n </PagePurpose>\n {loading ? (\n <p role=\"status\" className=\"text-sm text-text-muted\">\n Loading settings…\n </p>\n ) : error ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {error.message}\n </p>\n ) : !installation ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"Installation not found\"\n description=\"There's no configuration to show — this installation may have been removed. Return to your installed apps to pick another.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to installed apps\n </button>\n }\n />\n ) : (\n <EmphasisPanel className=\"space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Settings className=\"size-5 text-text-primary\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('pages.appSettings.configSnapshot', {\n defaultValue: 'Configuration snapshot',\n })}\n </h2>\n </div>\n\n <p className=\"text-sm text-text-muted\">\n {t('pages.appSettings.configDescription', {\n defaultValue:\n 'Store-level configuration for this installation is read-only here.',\n })}\n </p>\n\n <ShowMore collapsedHeight={140} label=\"configuration details\">\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">Manifest</p>\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-lg border border-border-seam bg-bg-sunken p-3 text-xs text-text-muted\">\n {JSON.stringify(installation.manifest ?? {}, null, 2)}\n </pre>\n </div>\n\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">\n Metadata &amp; Context\n </p>\n <pre className=\"overflow-x-auto whitespace-pre-wrap rounded-lg border border-border-seam bg-bg-sunken p-3 text-xs text-text-muted\">\n {JSON.stringify(\n {\n metadata: installation.metadata ?? {},\n context: installation.context ?? {},\n version: installation.version,\n status: installation.status,\n },\n null,\n 2\n )}\n </pre>\n </div>\n </div>\n </ShowMore>\n\n <div className=\"flex flex-wrap gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed/${installation.id}`)}\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {t('pages.appSettings.viewInstallation', { defaultValue: 'View Installation' })}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/marketplace/product/${installation.productId}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text hover:opacity-90\"\n >\n {t('pages.appDetail.viewProduct', { defaultValue: 'View Product' })}\n </button>\n </div>\n </EmphasisPanel>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;AAQA,IAAa,UAA4B;CACvC,IAAM,IAAW,GAAa,EACxB,EAAE,gBAAa,GAAU,EACzB,EAAE,sBAAmB,GAAuC,EAC5D,EAAE,iBAAc,YAAS,aAAU,EAAgB,EAAe,EAClE,EAAE,SAAM,GAAS;AAEvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GAAQ,WAAU;aAChB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAS,GAAG;KAC3B,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;KACzB,CAAA,EACT,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAE,2BAA2B,EAAE,cAAc,gBAAgB,CAAC;KAC5D,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAE,8BAA8B,EAC/B,cAAc,6CACf,CAAC;KACA,CAAA,CACA,EAAA,CAAA,CACF;;GACC,CAAA,EAET,kBAAC,QAAD;GAAM,WAAU;aACd,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAa,WAAU;eAAO;KAKhB,CAAA,EACb,IACC,kBAAC,KAAD;KAAG,MAAK;KAAS,WAAU;eAA0B;KAEjD,CAAA,GACF,IACF,kBAAC,KAAD;KAAG,MAAK;KAAQ,WAAU;eACvB,EAAM;KACL,CAAA,GACD,IAgBH,kBAAC,GAAD;KAAe,WAAU;eAAzB;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,4BAA6B,CAAA,EACjD,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAE,oCAAoC,EACrC,cAAc,0BACf,CAAC;QACC,CAAA,CACD;;MAEN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAE,uCAAuC,EACxC,cACE,sEACH,CAAC;OACA,CAAA;MAEJ,kBAAC,GAAD;OAAU,iBAAiB;OAAK,OAAM;iBACpC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAA6C;SAAY,CAAA,EACtE,kBAAC,OAAD;SAAK,WAAU;mBACZ,KAAK,UAAU,EAAa,YAAY,EAAE,EAAE,MAAM,EAAE;SACjD,CAAA,CACF,EAAA,CAAA,EAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAA6C;SAEtD,CAAA,EACJ,kBAAC,OAAD;SAAK,WAAU;mBACZ,KAAK,UACJ;UACE,UAAU,EAAa,YAAY,EAAE;UACrC,SAAS,EAAa,WAAW,EAAE;UACnC,SAAS,EAAa;UACtB,QAAQ,EAAa;UACtB,EACD,MACA,EACD;SACG,CAAA,CACF,EAAA,CAAA,CACF;;OACG,CAAA;MAEX,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAS,GAAG,EAAS,aAAa,EAAa,KAAK;QACnE,WAAU;kBAET,EAAE,sCAAsC,EAAE,cAAc,qBAAqB,CAAC;QACxE,CAAA,EACT,kBAAC,UAAD;QACE,MAAK;QACL,eACE,EAAS,GAAG,EAAS,uBAAuB,EAAa,YAAY;QAEvE,WAAU;kBAET,EAAE,+BAA+B,EAAE,cAAc,gBAAgB,CAAC;QAC5D,CAAA,CACL;;MACQ;SA/EhB,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,GAAG,EAAS,YAAY;MAChD,WAAU;gBACX;MAEQ,CAAA;KAEX,CAAA,CAoEA;;GACD,CAAA,CACH"}
@@ -5,7 +5,7 @@ import { useState as r } from "react";
5
5
  import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
6
  import { ArrowLeft as a, Loader2 as o, Minus as s, Plus as c, ShoppingBag as l, ShoppingCart as u, Tag as d, Trash2 as f, Zap as p } from "lucide-react";
7
7
  import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
8
- import { useNavigate as _ } from "react-router-dom";
8
+ import { useNavigate as _ } from "react-router";
9
9
  import { isQuotaExhaustedError as v } from "@burdenoff/fe-libs/shared/utils";
10
10
  import { EmphasisPanel as y, IllustratedEmptyState as b, PagePurpose as x } from "@burdenoff/fe-libs/ui";
11
11
  import { useEventBus as S } from "@burdenoff/fe-libs/shared/events";