@burdenoff/microfe-devportal 2026.916.8 → 2026.917.2

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/devportal/components/ManifestViewer.d.ts +12 -0
  2. package/dist/devportal/components/ManifestViewer.d.ts.map +1 -0
  3. package/dist/devportal/components/ManifestViewer.js +58 -0
  4. package/dist/devportal/components/ManifestViewer.js.map +1 -0
  5. package/dist/devportal/components/index.d.ts +1 -0
  6. package/dist/devportal/components/index.d.ts.map +1 -1
  7. package/dist/devportal/components/index.js +1 -0
  8. package/dist/devportal/hooks/index.d.ts +1 -0
  9. package/dist/devportal/hooks/index.d.ts.map +1 -1
  10. package/dist/devportal/hooks/index.js +1 -0
  11. package/dist/devportal/hooks/useProducts.d.ts +18 -0
  12. package/dist/devportal/hooks/useProducts.d.ts.map +1 -0
  13. package/dist/devportal/hooks/useProducts.js +14 -0
  14. package/dist/devportal/hooks/useProducts.js.map +1 -0
  15. package/dist/devportal/pages/AdminReviewPage.d.ts.map +1 -1
  16. package/dist/devportal/pages/AdminReviewPage.js +54 -64
  17. package/dist/devportal/pages/AdminReviewPage.js.map +1 -1
  18. package/dist/devportal/pages/ApplicationVersionsPage.d.ts.map +1 -1
  19. package/dist/devportal/pages/ApplicationVersionsPage.js +304 -272
  20. package/dist/devportal/pages/ApplicationVersionsPage.js.map +1 -1
  21. package/dist/devportal/pages/CreateApplicationPage.d.ts.map +1 -1
  22. package/dist/devportal/pages/CreateApplicationPage.js +137 -156
  23. package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
  24. package/dist/devportal/pages/EditApplicationPage.d.ts.map +1 -1
  25. package/dist/devportal/pages/EditApplicationPage.js +271 -290
  26. package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
  27. package/dist/generated/global-operations.d.ts +39 -0
  28. package/dist/generated/global-operations.d.ts.map +1 -1
  29. package/dist/generated/global-operations.js +72 -54
  30. package/dist/generated/global-operations.js.map +1 -1
  31. package/dist/translations/en.d.ts +4 -1
  32. package/dist/translations/en.d.ts.map +1 -1
  33. package/dist/translations/en.js +5 -2
  34. package/dist/translations/en.js.map +1 -1
  35. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AdminReviewPage.js","names":[],"sources":["../../../src/devportal/pages/AdminReviewPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n XCircle,\n Clock,\n Search,\n Loader2,\n AlertCircle,\n Eye,\n ChevronDown,\n ChevronUp,\n Download,\n FileJson,\n GitBranch,\n Package,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport {\n useGetApplicationsQuery,\n useApproveApplicationMutation,\n useRejectApplicationMutation,\n useGetApplicationForReviewQuery,\n useGetApplicationsWithPendingVersionsQuery,\n useApproveApplicationVersionMutation,\n useRejectApplicationVersionMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type ApplicationVersionFieldsFragment,\n type GetApplicationsWithPendingVersionsQuery,\n} from \"../hooks\";\n\ntype AppWithPendingVersions = NonNullable<\n GetApplicationsWithPendingVersionsQuery[\"applicationsWithPendingVersions\"][\"edges\"][number][\"node\"]\n>;\n\n// ============================================================================\n// Manifest Viewer\n// ============================================================================\n\ninterface ManifestViewerProps {\n manifest: Record<string, unknown>;\n tr: (key: string, fallback: string) => string;\n}\n\nconst ManifestViewer: FC<ManifestViewerProps> = ({ manifest, tr }) => {\n const [open, setOpen] = useState(false);\n return (\n <div>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary\"\n >\n <FileJson size={13} />\n {open\n ? tr(\"devportal.hideManifest\", \"Hide manifest\")\n : tr(\"devportal.viewManifest\", \"View manifest.json\")}\n </button>\n {open && (\n <pre className=\"mt-2 max-h-48 overflow-auto rounded-md bg-bg-sunken p-3 text-xs font-mono whitespace-pre-wrap break-all\">\n {JSON.stringify(manifest, null, 2)}\n </pre>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// ZIP verification banner (Node / Workflow apps only)\n// ============================================================================\n\nconst ZIP_TYPE_LABELS: Partial<Record<string, string>> = {\n NODE: \"Node package\",\n WORKFLOW: \"Workflow bundle\",\n};\n\ninterface ZipDownloadProps {\n appType: string;\n packageUrl: string | null | undefined;\n packageSize: string | null | undefined;\n tr: (key: string, fallback: string) => string;\n}\n\nconst ZipDownload: FC<ZipDownloadProps> = ({\n appType,\n packageUrl,\n packageSize,\n tr,\n}) => {\n const label = ZIP_TYPE_LABELS[appType];\n if (!label) return null;\n\n return (\n <div className=\"rounded-lg border border-status-warning-bg bg-status-warning-bg-subtle p-3 space-y-2\">\n <div className=\"flex items-center gap-2 text-xs font-medium text-status-warning-text\">\n <Package size={13} className=\"flex-shrink-0\" />\n {tr(\n \"devportal.zipVerificationRequired\",\n \"ZIP verification required — download and test before approving\",\n )}\n </div>\n {packageUrl ? (\n <a\n href={packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Download size={14} />\n {tr(\"devportal.download\", \"Download\")} {label}\n {packageSize && (\n <span className=\"opacity-70 text-xs\">({packageSize})</span>\n )}\n </a>\n ) : (\n <p className=\"text-xs text-status-error-text flex items-center gap-1.5\">\n <AlertCircle size={12} />\n {tr(\n \"devportal.noZipUploaded\",\n \"No ZIP uploaded — developer must upload before this can be approved\",\n )}\n </p>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Review Row Component\n// ============================================================================\n\ninterface ReviewRowProps {\n app: ApplicationFieldsFragment;\n onApprove: (id: string, notes: string) => void;\n onReject: (id: string, notes: string) => void;\n processing: boolean;\n hasPendingVersions: boolean;\n}\n\nconst ReviewRow: FC<ReviewRowProps> = ({\n app,\n onApprove,\n onReject,\n processing,\n hasPendingVersions,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n const [reviewNotes, setReviewNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n const { data: versionData } = useGetApplicationForReviewQuery({\n variables: { id: app.id },\n skip: !expanded,\n fetchPolicy: \"cache-first\",\n });\n const latestVersion = versionData?.application?.versions?.edges?.[0]?.node;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n {/* Header row */}\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-xs font-medium text-status-warning-text\">\n {app.status}\n </span>\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {app.appType.replace(/_/g, \" \")}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {app.submittedAt\n ? new Date(app.submittedAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })\n : tr(\"devportal.unknown\", \"Unknown\")}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n title={tr(\"devportal.viewAppDetails\", \"View app details\")}\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {/* Expanded review panel */}\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {/* Version approval hint — shown when this app also has pending versions */}\n {hasPendingVersions && (\n <div className=\"flex items-start gap-2 rounded-md border border-status-warning-bg bg-status-warning-bg-subtle px-3 py-2.5 text-xs text-status-warning-text\">\n <GitBranch\n size={14}\n className=\"mt-0.5 shrink-0\"\n aria-hidden=\"true\"\n />\n <span>\n This app has a pending version. Approve the version in the{\" \"}\n <strong>Version Updates</strong> section below before approving\n the app — the version must be live before the app can go public.\n </span>\n </div>\n )}\n\n {/* Submission notes from developer */}\n {app.reviewNotes && (\n <div className=\"rounded-md border bg-bg-surface p-3\">\n <p className=\"text-xs font-medium text-text-secondary mb-1\">\n {tr(\"devportal.developerNotes\", \"Developer Notes\")}\n </p>\n <p className=\"text-sm\">{app.reviewNotes}</p>\n </div>\n )}\n\n {/* App details summary */}\n <div className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.category\", \"Category\")}:{\" \"}\n </span>\n <span>{app.category ?? \"—\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.pricing\", \"Pricing\")}:{\" \"}\n </span>\n <span>{app.pricingType ?? \"FREE\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.website\", \"Website\")}:{\" \"}\n </span>\n {app.websiteUrl ? (\n <a\n href={app.websiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span>—</span>\n )}\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.privacyPolicy\", \"Privacy Policy\")}:{\" \"}\n </span>\n {app.privacyPolicyUrl ? (\n <a\n href={app.privacyPolicyUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span className=\"text-status-error-text\">\n {tr(\"devportal.missing\", \"Missing\")}\n </span>\n )}\n </div>\n </div>\n\n {/* Latest version package for review */}\n {latestVersion && (\n <div className=\"rounded-md border bg-bg-surface p-3 space-y-3\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.latestVersion\", \"Latest Version\")}:{\" \"}\n <span className=\"font-mono text-text-primary\">\n {latestVersion.version}\n </span>\n </p>\n\n {/* ZIP download — prominent banner for Node/Workflow, plain link otherwise */}\n {ZIP_TYPE_LABELS[app.appType] ? (\n <ZipDownload\n appType={app.appType}\n packageUrl={latestVersion.packageUrl}\n packageSize={latestVersion.packageSize}\n tr={tr}\n />\n ) : (\n latestVersion.packageUrl && (\n <div className=\"flex flex-wrap gap-2\">\n <a\n href={latestVersion.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {latestVersion.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({latestVersion.packageSize})\n </span>\n )}\n </a>\n </div>\n )\n )}\n\n {latestVersion.manifest && (\n <ManifestViewer\n manifest={latestVersion.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n </div>\n )}\n\n {/* Reviewer notes input */}\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={reviewNotes}\n onChange={(e) => setReviewNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(app.id, reviewNotes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n title={\n !approvePerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <CheckCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.approve\", \"Approve\")}\n </button>\n <button\n onClick={() => onReject(app.id, reviewNotes)}\n disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n aria-disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n title={\n !rejectPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : !reviewNotes.trim()\n ? tr(\n \"devportal.notesRequiredForRejection\",\n \"Notes required for rejection\",\n )\n : undefined\n }\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <XCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.reject\", \"Reject\")}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Version Review Row Component\n// ============================================================================\n\ninterface VersionReviewRowProps {\n app: AppWithPendingVersions;\n onApproveVersion: (versionId: string, appId: string, notes: string) => void;\n onRejectVersion: (versionId: string, appId: string, reason: string) => void;\n processingVersionIds: Set<string>;\n}\n\nconst VersionReviewRow: FC<VersionReviewRowProps> = ({\n app,\n onApproveVersion,\n onRejectVersion,\n processingVersionIds,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n\n const pendingVersions = app.versions.edges\n .map((e) => e.node)\n .filter((v) => v.status === \"pending_review\");\n\n if (pendingVersions.length === 0) return null;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text\">\n {pendingVersions.length} version\n {pendingVersions.length !== 1 ? \"s\" : \"\"} pending\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {pendingVersions.map((version) => (\n <VersionCard\n key={version.id}\n version={version}\n appId={app.id}\n appType={app.appType}\n onApprove={onApproveVersion}\n onReject={onRejectVersion}\n processing={processingVersionIds.has(version.id)}\n tr={tr}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\ntype PendingVersion =\n AppWithPendingVersions[\"versions\"][\"edges\"][number][\"node\"];\n\ninterface VersionCardProps {\n version: PendingVersion;\n appId: string;\n appType: string;\n onApprove: (versionId: string, appId: string, notes: string) => void;\n onReject: (versionId: string, appId: string, reason: string) => void;\n processing: boolean;\n tr: (key: string, fallback: string) => string;\n}\n\nconst VersionCard: FC<VersionCardProps> = ({\n version,\n appId,\n appType,\n onApprove,\n onReject,\n processing,\n tr,\n}) => {\n const [notes, setNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n return (\n <div className=\"rounded-md border bg-bg-surface p-4 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <GitBranch size={14} className=\"text-text-secondary flex-shrink-0\" />\n <span className=\"font-mono font-semibold text-sm\">\n {version.version}\n </span>\n {version.isLatest && (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n Latest\n </span>\n )}\n <span className=\"text-xs text-text-secondary ml-auto\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {new Date(version.createdAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })}\n </span>\n </div>\n\n {version.releaseNotes && (\n <p className=\"text-sm text-text-secondary\">{version.releaseNotes}</p>\n )}\n\n {ZIP_TYPE_LABELS[appType] ? (\n <ZipDownload\n appType={appType}\n packageUrl={version.packageUrl}\n packageSize={version.packageSize}\n tr={tr}\n />\n ) : (\n version.packageUrl && (\n <div className=\"flex flex-wrap gap-2\">\n <a\n href={version.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {version.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({version.packageSize})\n </span>\n )}\n </a>\n </div>\n )\n )}\n\n {version.manifest && (\n <ManifestViewer\n manifest={version.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(version.id, appId, notes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <CheckCircle size={14} />\n )}\n {tr(\"devportal.approveVersion\", \"Approve Version\")}\n </button>\n <button\n onClick={() => onReject(version.id, appId, notes)}\n disabled={processing || !notes.trim() || !rejectPerm.allowed}\n aria-disabled={processing || !notes.trim() || !rejectPerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <XCircle size={14} />\n )}\n {tr(\"devportal.rejectVersion\", \"Reject Version\")}\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Admin Review Page\n// ============================================================================\n\nexport const AdminReviewPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());\n const [processingVersionIds, setProcessingVersionIds] = useState<Set<string>>(\n new Set(),\n );\n const [actionError, setActionError] = useState<string | null>(null);\n const [actionSuccess, setActionSuccess] = useState<string | null>(null);\n\n const addProcessing = (id: string) =>\n setProcessingIds((prev) => new Set(prev).add(id));\n const removeProcessing = (id: string) =>\n setProcessingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n const addProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => new Set(prev).add(id));\n const removeProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: { status: \"SUBMITTED\" as const, limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const { data: pendingVersionData, refetch: refetchPendingVersions } =\n useGetApplicationsWithPendingVersionsQuery({\n variables: { limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const [approveApplication] = useApproveApplicationMutation();\n const [rejectApplication] = useRejectApplicationMutation();\n const [approveApplicationVersion] = useApproveApplicationVersionMutation();\n const [rejectApplicationVersion] = useRejectApplicationVersionMutation();\n\n const apps =\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node,\n ) ?? [];\n\n const appsWithPendingVersions =\n pendingVersionData?.applicationsWithPendingVersions?.edges?.map(\n (edge: { node: AppWithPendingVersions }) => edge.node,\n ) ?? [];\n\n const pendingVersionAppIds = new Set(\n appsWithPendingVersions.map((a: AppWithPendingVersions) => a.id),\n );\n\n const filteredApps = searchQuery\n ? apps.filter(\n (app: ApplicationFieldsFragment) =>\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (app.shortDescription ?? \"\")\n .toLowerCase()\n .includes(searchQuery.toLowerCase()),\n )\n : apps;\n\n const handleApprove = async (id: string, notes: string) => {\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplication({\n variables: { id, notes: notes || undefined },\n });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\n \"devportal.applicationApprovedSuccess\",\n \"Application approved successfully.\",\n ),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToApproveApplication\",\n \"Failed to approve application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleReject = async (id: string, notes: string) => {\n if (!notes.trim()) return;\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplication({ variables: { id, notes } });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\"devportal.applicationRejected\", \"Application rejected.\"),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToRejectApplication\",\n \"Failed to reject application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleApproveVersion = async (\n versionId: string,\n _appId: string,\n notes: string,\n ) => {\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplicationVersion({\n variables: { id: versionId, notes: notes || undefined },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionApprovedSuccess\",\n \"Version approved and published.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToApproveVersion\", \"Failed to approve version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n const handleRejectVersion = async (\n versionId: string,\n _appId: string,\n reason: string,\n ) => {\n if (!reason.trim()) return;\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplicationVersion({\n variables: { id: versionId, reason },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionRejectedSuccess\",\n \"Version rejected and returned to draft.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToRejectVersion\", \"Failed to reject version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <p className=\"mt-2 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.retry\", \"Retry\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.REVIEW_MANAGE}>\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueFullDescription\",\n \"Review submitted applications for approval or rejection\",\n )}\n purpose={tr(\n \"devportal.appReviewQueuePurpose\",\n \"The moderation queue for apps developers have submitted to the marketplace. Open each submission to check its listing, permissions, and behaviour against the guidelines, then approve it to go live or reject it with feedback.\",\n )}\n actions={\n <button\n onClick={() => {\n void refetch();\n void refetchPendingVersions();\n }}\n className=\"rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {tr(\"devportal.refresh\", \"Refresh\")}\n </button>\n }\n >\n {/* Stats bar */}\n <div className=\"flex items-center gap-6 rounded-card border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center gap-2\">\n <Clock size={18} className=\"text-status-warning-text\" />\n <span className=\"text-sm font-medium\">\n {apps.length} {tr(\"devportal.pendingReview\", \"pending review\")}\n </span>\n </div>\n {appsWithPendingVersions.length > 0 && (\n <div className=\"flex items-center gap-2\">\n <GitBranch size={18} className=\"text-status-info-text\" />\n <span className=\"text-sm font-medium\">\n {appsWithPendingVersions.length}{\" \"}\n {tr(\"devportal.withPendingVersions\", \"with pending versions\")}\n </span>\n </div>\n )}\n {(processingIds.size > 0 || processingVersionIds.size > 0) && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n {tr(\"devportal.processing\", \"Processing...\")}\n </div>\n )}\n </div>\n\n {/* Feedback messages */}\n {actionSuccess && (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-subtle bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\">\n <CheckCircle size={16} />\n {actionSuccess}\n </div>\n )}\n {actionError && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm text-status-error-text\">\n <AlertCircle size={16} />\n {actionError}\n </div>\n )}\n\n {/* Search */}\n {apps.length > 0 && (\n <div className=\"relative max-w-sm\">\n <Search\n size={14}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchSubmittedApps\",\n \"Search submitted apps...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n )}\n\n {/* New app submissions */}\n <PageSection\n title={tr(\"devportal.newAppSubmissions\", \"New App Submissions\")}\n >\n {filteredApps.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CheckCircle size={32} />}\n title={tr(\n \"devportal.noAppsPendingReview\",\n \"No apps pending review\",\n )}\n description={\n searchQuery\n ? tr(\n \"devportal.noAppsMatchSearch\",\n \"No apps match your search\",\n )\n : tr(\n \"devportal.allApplicationsReviewed\",\n \"All submitted applications have been reviewed\",\n )\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredApps.map((app: ApplicationFieldsFragment) => (\n <ReviewRow\n key={app.id}\n app={app}\n onApprove={handleApprove}\n onReject={handleReject}\n processing={processingIds.has(app.id)}\n hasPendingVersions={pendingVersionAppIds.has(app.id)}\n />\n ))}\n </div>\n )}\n </PageSection>\n\n {/* Version updates for published apps */}\n <PageSection\n title={tr(\n \"devportal.versionUpdates\",\n \"Version Updates (Published Apps)\",\n )}\n >\n {appsWithPendingVersions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<GitBranch size={32} />}\n title={tr(\n \"devportal.noPendingVersions\",\n \"No pending version updates\",\n )}\n description={tr(\n \"devportal.allVersionsReviewed\",\n \"All version updates from published apps have been reviewed\",\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {appsWithPendingVersions.map((app: AppWithPendingVersions) => (\n <VersionReviewRow\n key={app.id}\n app={app}\n onApproveVersion={handleApproveVersion}\n onRejectVersion={handleRejectVersion}\n processingVersionIds={processingVersionIds}\n />\n ))}\n </div>\n )}\n </PageSection>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AdminReviewPage.js","names":[],"sources":["../../../src/devportal/pages/AdminReviewPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n XCircle,\n Clock,\n Search,\n Loader2,\n AlertCircle,\n Eye,\n ChevronDown,\n ChevronUp,\n Download,\n GitBranch,\n Package,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard, ManifestViewer } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport {\n useGetApplicationsQuery,\n useApproveApplicationMutation,\n useRejectApplicationMutation,\n useGetApplicationForReviewQuery,\n useGetApplicationsWithPendingVersionsQuery,\n useApproveApplicationVersionMutation,\n useRejectApplicationVersionMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type ApplicationVersionFieldsFragment,\n type GetApplicationsWithPendingVersionsQuery,\n} from \"../hooks\";\n\ntype AppWithPendingVersions = NonNullable<\n GetApplicationsWithPendingVersionsQuery[\"applicationsWithPendingVersions\"][\"edges\"][number][\"node\"]\n>;\n\n// ============================================================================\n// ZIP verification banner (Node / Workflow apps only)\n// ============================================================================\n\nconst ZIP_TYPE_LABELS: Partial<Record<string, string>> = {\n NODE: \"Node package\",\n WORKFLOW: \"Workflow bundle\",\n};\n\ninterface ZipDownloadProps {\n appType: string;\n packageUrl: string | null | undefined;\n packageSize: string | null | undefined;\n tr: (key: string, fallback: string) => string;\n}\n\nconst ZipDownload: FC<ZipDownloadProps> = ({\n appType,\n packageUrl,\n packageSize,\n tr,\n}) => {\n const label = ZIP_TYPE_LABELS[appType];\n if (!label) return null;\n\n return (\n <div className=\"rounded-lg border border-status-warning-bg bg-status-warning-bg-subtle p-3 space-y-2\">\n <div className=\"flex items-center gap-2 text-xs font-medium text-status-warning-text\">\n <Package size={13} className=\"flex-shrink-0\" />\n {tr(\n \"devportal.zipVerificationRequired\",\n \"ZIP verification required — download and test before approving\",\n )}\n </div>\n {packageUrl ? (\n <a\n href={packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Download size={14} />\n {tr(\"devportal.download\", \"Download\")} {label}\n {packageSize && (\n <span className=\"opacity-70 text-xs\">({packageSize})</span>\n )}\n </a>\n ) : (\n <p className=\"text-xs text-status-error-text flex items-center gap-1.5\">\n <AlertCircle size={12} />\n {tr(\n \"devportal.noZipUploaded\",\n \"No ZIP uploaded — developer must upload before this can be approved\",\n )}\n </p>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Review Row Component\n// ============================================================================\n\ninterface ReviewRowProps {\n app: ApplicationFieldsFragment;\n onApprove: (id: string, notes: string) => void;\n onReject: (id: string, notes: string) => void;\n processing: boolean;\n hasPendingVersions: boolean;\n}\n\nconst ReviewRow: FC<ReviewRowProps> = ({\n app,\n onApprove,\n onReject,\n processing,\n hasPendingVersions,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n const [reviewNotes, setReviewNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n const { data: versionData } = useGetApplicationForReviewQuery({\n variables: { id: app.id },\n skip: !expanded,\n fetchPolicy: \"cache-first\",\n });\n const latestVersion = versionData?.application?.versions?.edges?.[0]?.node;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n {/* Header row */}\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-xs font-medium text-status-warning-text\">\n {app.status}\n </span>\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {app.appType.replace(/_/g, \" \")}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {app.submittedAt\n ? new Date(app.submittedAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })\n : tr(\"devportal.unknown\", \"Unknown\")}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n title={tr(\"devportal.viewAppDetails\", \"View app details\")}\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {/* Expanded review panel */}\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {/* Version approval hint — shown when this app also has pending versions */}\n {hasPendingVersions && (\n <div className=\"flex items-start gap-2 rounded-md border border-status-warning-bg bg-status-warning-bg-subtle px-3 py-2.5 text-xs text-status-warning-text\">\n <GitBranch\n size={14}\n className=\"mt-0.5 shrink-0\"\n aria-hidden=\"true\"\n />\n <span>\n This app has a pending version. Approve the version in the{\" \"}\n <strong>Version Updates</strong> section below before approving\n the app — the version must be live before the app can go public.\n </span>\n </div>\n )}\n\n {/* Submission notes from developer */}\n {app.reviewNotes && (\n <div className=\"rounded-md border bg-bg-surface p-3\">\n <p className=\"text-xs font-medium text-text-secondary mb-1\">\n {tr(\"devportal.developerNotes\", \"Developer Notes\")}\n </p>\n <p className=\"text-sm\">{app.reviewNotes}</p>\n </div>\n )}\n\n {/* App details summary */}\n <div className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.category\", \"Category\")}:{\" \"}\n </span>\n <span>{app.category ?? \"—\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.pricing\", \"Pricing\")}:{\" \"}\n </span>\n <span>{app.pricingType ?? \"FREE\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.website\", \"Website\")}:{\" \"}\n </span>\n {app.websiteUrl ? (\n <a\n href={app.websiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span>—</span>\n )}\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.privacyPolicy\", \"Privacy Policy\")}:{\" \"}\n </span>\n {app.privacyPolicyUrl ? (\n <a\n href={app.privacyPolicyUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span className=\"text-status-error-text\">\n {tr(\"devportal.missing\", \"Missing\")}\n </span>\n )}\n </div>\n </div>\n\n {/* Latest version package for review */}\n {latestVersion && (\n <div className=\"rounded-md border bg-bg-surface p-3 space-y-3\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.latestVersion\", \"Latest Version\")}:{\" \"}\n <span className=\"font-mono text-text-primary\">\n {latestVersion.version}\n </span>\n </p>\n\n {/* ZIP download — prominent banner for Node/Workflow, plain link otherwise */}\n {ZIP_TYPE_LABELS[app.appType] ? (\n <ZipDownload\n appType={app.appType}\n packageUrl={latestVersion.packageUrl}\n packageSize={latestVersion.packageSize}\n tr={tr}\n />\n ) : (\n latestVersion.packageUrl && (\n <div className=\"flex flex-wrap gap-2\">\n <a\n href={latestVersion.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {latestVersion.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({latestVersion.packageSize})\n </span>\n )}\n </a>\n </div>\n )\n )}\n\n {latestVersion.manifest && (\n <ManifestViewer\n manifest={latestVersion.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n </div>\n )}\n\n {/* Reviewer notes input */}\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={reviewNotes}\n onChange={(e) => setReviewNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(app.id, reviewNotes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n title={\n !approvePerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <CheckCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.approve\", \"Approve\")}\n </button>\n <button\n onClick={() => onReject(app.id, reviewNotes)}\n disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n aria-disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n title={\n !rejectPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : !reviewNotes.trim()\n ? tr(\n \"devportal.notesRequiredForRejection\",\n \"Notes required for rejection\",\n )\n : undefined\n }\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <XCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.reject\", \"Reject\")}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Version Review Row Component\n// ============================================================================\n\ninterface VersionReviewRowProps {\n app: AppWithPendingVersions;\n onApproveVersion: (versionId: string, appId: string, notes: string) => void;\n onRejectVersion: (versionId: string, appId: string, reason: string) => void;\n processingVersionIds: Set<string>;\n}\n\nconst VersionReviewRow: FC<VersionReviewRowProps> = ({\n app,\n onApproveVersion,\n onRejectVersion,\n processingVersionIds,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n\n const pendingVersions = app.versions.edges\n .map((e) => e.node)\n .filter((v) => v.status === \"pending_review\");\n\n if (pendingVersions.length === 0) return null;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text\">\n {pendingVersions.length} version\n {pendingVersions.length !== 1 ? \"s\" : \"\"} pending\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {pendingVersions.map((version) => (\n <VersionCard\n key={version.id}\n version={version}\n appId={app.id}\n appType={app.appType}\n onApprove={onApproveVersion}\n onReject={onRejectVersion}\n processing={processingVersionIds.has(version.id)}\n tr={tr}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\ntype PendingVersion =\n AppWithPendingVersions[\"versions\"][\"edges\"][number][\"node\"];\n\ninterface VersionCardProps {\n version: PendingVersion;\n appId: string;\n appType: string;\n onApprove: (versionId: string, appId: string, notes: string) => void;\n onReject: (versionId: string, appId: string, reason: string) => void;\n processing: boolean;\n tr: (key: string, fallback: string) => string;\n}\n\nconst VersionCard: FC<VersionCardProps> = ({\n version,\n appId,\n appType,\n onApprove,\n onReject,\n processing,\n tr,\n}) => {\n const [notes, setNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n return (\n <div className=\"rounded-md border bg-bg-surface p-4 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <GitBranch size={14} className=\"text-text-secondary flex-shrink-0\" />\n <span className=\"font-mono font-semibold text-sm\">\n {version.version}\n </span>\n {version.isLatest && (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n Latest\n </span>\n )}\n <span className=\"text-xs text-text-secondary ml-auto\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {new Date(version.createdAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })}\n </span>\n </div>\n\n {version.releaseNotes && (\n <p className=\"text-sm text-text-secondary\">{version.releaseNotes}</p>\n )}\n\n {ZIP_TYPE_LABELS[appType] ? (\n <ZipDownload\n appType={appType}\n packageUrl={version.packageUrl}\n packageSize={version.packageSize}\n tr={tr}\n />\n ) : (\n version.packageUrl && (\n <div className=\"flex flex-wrap gap-2\">\n <a\n href={version.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {version.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({version.packageSize})\n </span>\n )}\n </a>\n </div>\n )\n )}\n\n {version.manifest && (\n <ManifestViewer\n manifest={version.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(version.id, appId, notes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <CheckCircle size={14} />\n )}\n {tr(\"devportal.approveVersion\", \"Approve Version\")}\n </button>\n <button\n onClick={() => onReject(version.id, appId, notes)}\n disabled={processing || !notes.trim() || !rejectPerm.allowed}\n aria-disabled={processing || !notes.trim() || !rejectPerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <XCircle size={14} />\n )}\n {tr(\"devportal.rejectVersion\", \"Reject Version\")}\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Admin Review Page\n// ============================================================================\n\nexport const AdminReviewPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());\n const [processingVersionIds, setProcessingVersionIds] = useState<Set<string>>(\n new Set(),\n );\n const [actionError, setActionError] = useState<string | null>(null);\n const [actionSuccess, setActionSuccess] = useState<string | null>(null);\n\n const addProcessing = (id: string) =>\n setProcessingIds((prev) => new Set(prev).add(id));\n const removeProcessing = (id: string) =>\n setProcessingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n const addProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => new Set(prev).add(id));\n const removeProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: { status: \"SUBMITTED\" as const, limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const { data: pendingVersionData, refetch: refetchPendingVersions } =\n useGetApplicationsWithPendingVersionsQuery({\n variables: { limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const [approveApplication] = useApproveApplicationMutation();\n const [rejectApplication] = useRejectApplicationMutation();\n const [approveApplicationVersion] = useApproveApplicationVersionMutation();\n const [rejectApplicationVersion] = useRejectApplicationVersionMutation();\n\n const apps =\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node,\n ) ?? [];\n\n const appsWithPendingVersions =\n pendingVersionData?.applicationsWithPendingVersions?.edges?.map(\n (edge: { node: AppWithPendingVersions }) => edge.node,\n ) ?? [];\n\n const pendingVersionAppIds = new Set(\n appsWithPendingVersions.map((a: AppWithPendingVersions) => a.id),\n );\n\n const filteredApps = searchQuery\n ? apps.filter(\n (app: ApplicationFieldsFragment) =>\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (app.shortDescription ?? \"\")\n .toLowerCase()\n .includes(searchQuery.toLowerCase()),\n )\n : apps;\n\n const handleApprove = async (id: string, notes: string) => {\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplication({\n variables: { id, notes: notes || undefined },\n });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\n \"devportal.applicationApprovedSuccess\",\n \"Application approved successfully.\",\n ),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToApproveApplication\",\n \"Failed to approve application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleReject = async (id: string, notes: string) => {\n if (!notes.trim()) return;\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplication({ variables: { id, notes } });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\"devportal.applicationRejected\", \"Application rejected.\"),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToRejectApplication\",\n \"Failed to reject application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleApproveVersion = async (\n versionId: string,\n _appId: string,\n notes: string,\n ) => {\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplicationVersion({\n variables: { id: versionId, notes: notes || undefined },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionApprovedSuccess\",\n \"Version approved and published.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToApproveVersion\", \"Failed to approve version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n const handleRejectVersion = async (\n versionId: string,\n _appId: string,\n reason: string,\n ) => {\n if (!reason.trim()) return;\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplicationVersion({\n variables: { id: versionId, reason },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionRejectedSuccess\",\n \"Version rejected and returned to draft.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToRejectVersion\", \"Failed to reject version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <p className=\"mt-2 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.retry\", \"Retry\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.REVIEW_MANAGE}>\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueFullDescription\",\n \"Review submitted applications for approval or rejection\",\n )}\n purpose={tr(\n \"devportal.appReviewQueuePurpose\",\n \"The moderation queue for apps developers have submitted to the marketplace. Open each submission to check its listing, permissions, and behaviour against the guidelines, then approve it to go live or reject it with feedback.\",\n )}\n actions={\n <button\n onClick={() => {\n void refetch();\n void refetchPendingVersions();\n }}\n className=\"rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {tr(\"devportal.refresh\", \"Refresh\")}\n </button>\n }\n >\n {/* Stats bar */}\n <div className=\"flex items-center gap-6 rounded-card border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center gap-2\">\n <Clock size={18} className=\"text-status-warning-text\" />\n <span className=\"text-sm font-medium\">\n {apps.length} {tr(\"devportal.pendingReview\", \"pending review\")}\n </span>\n </div>\n {appsWithPendingVersions.length > 0 && (\n <div className=\"flex items-center gap-2\">\n <GitBranch size={18} className=\"text-status-info-text\" />\n <span className=\"text-sm font-medium\">\n {appsWithPendingVersions.length}{\" \"}\n {tr(\"devportal.withPendingVersions\", \"with pending versions\")}\n </span>\n </div>\n )}\n {(processingIds.size > 0 || processingVersionIds.size > 0) && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n {tr(\"devportal.processing\", \"Processing...\")}\n </div>\n )}\n </div>\n\n {/* Feedback messages */}\n {actionSuccess && (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-subtle bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\">\n <CheckCircle size={16} />\n {actionSuccess}\n </div>\n )}\n {actionError && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm text-status-error-text\">\n <AlertCircle size={16} />\n {actionError}\n </div>\n )}\n\n {/* Search */}\n {apps.length > 0 && (\n <div className=\"relative max-w-sm\">\n <Search\n size={14}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchSubmittedApps\",\n \"Search submitted apps...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n )}\n\n {/* New app submissions */}\n <PageSection\n title={tr(\"devportal.newAppSubmissions\", \"New App Submissions\")}\n >\n {filteredApps.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CheckCircle size={32} />}\n title={tr(\n \"devportal.noAppsPendingReview\",\n \"No apps pending review\",\n )}\n description={\n searchQuery\n ? tr(\n \"devportal.noAppsMatchSearch\",\n \"No apps match your search\",\n )\n : tr(\n \"devportal.allApplicationsReviewed\",\n \"All submitted applications have been reviewed\",\n )\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredApps.map((app: ApplicationFieldsFragment) => (\n <ReviewRow\n key={app.id}\n app={app}\n onApprove={handleApprove}\n onReject={handleReject}\n processing={processingIds.has(app.id)}\n hasPendingVersions={pendingVersionAppIds.has(app.id)}\n />\n ))}\n </div>\n )}\n </PageSection>\n\n {/* Version updates for published apps */}\n <PageSection\n title={tr(\n \"devportal.versionUpdates\",\n \"Version Updates (Published Apps)\",\n )}\n >\n {appsWithPendingVersions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<GitBranch size={32} />}\n title={tr(\n \"devportal.noPendingVersions\",\n \"No pending version updates\",\n )}\n description={tr(\n \"devportal.allVersionsReviewed\",\n \"All version updates from published apps have been reviewed\",\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {appsWithPendingVersions.map((app: AppWithPendingVersions) => (\n <VersionReviewRow\n key={app.id}\n app={app}\n onApproveVersion={handleApproveVersion}\n onRejectVersion={handleRejectVersion}\n processingVersionIds={processingVersionIds}\n />\n ))}\n </div>\n )}\n </PageSection>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationVersionsPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/ApplicationVersionsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA0ChC,eAAO,MAAM,uBAAuB,EAAE,EA2iDrC,CAAC"}
1
+ {"version":3,"file":"ApplicationVersionsPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/ApplicationVersionsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA4EhC,eAAO,MAAM,uBAAuB,EAAE,EA0lDrC,CAAC"}