@burdenoff/microfe-devportal 2026.830.1 → 2026.831.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 (104) hide show
  1. package/dist/devportal/AdminDevportalRoot.d.ts.map +1 -1
  2. package/dist/devportal/AdminDevportalRoot.js +16 -19
  3. package/dist/devportal/AdminDevportalRoot.js.map +1 -1
  4. package/dist/devportal/DevportalRoot.d.ts.map +1 -1
  5. package/dist/devportal/DevportalRoot.js +36 -39
  6. package/dist/devportal/DevportalRoot.js.map +1 -1
  7. package/dist/devportal/components/AppCard.js +41 -41
  8. package/dist/devportal/components/ConfirmDialog.d.ts.map +1 -1
  9. package/dist/devportal/components/ConfirmDialog.js +19 -22
  10. package/dist/devportal/components/ConfirmDialog.js.map +1 -1
  11. package/dist/devportal/components/DataTable.d.ts.map +1 -1
  12. package/dist/devportal/components/DataTable.js +31 -34
  13. package/dist/devportal/components/DataTable.js.map +1 -1
  14. package/dist/devportal/components/FeatureFlaggedRoute.js +8 -8
  15. package/dist/devportal/components/FormField.d.ts.map +1 -1
  16. package/dist/devportal/components/FormField.js +26 -32
  17. package/dist/devportal/components/FormField.js.map +1 -1
  18. package/dist/devportal/components/GrantDelegationModal.js +64 -64
  19. package/dist/devportal/components/GrantDelegationModal.js.map +1 -1
  20. package/dist/devportal/components/LoadingState.d.ts.map +1 -1
  21. package/dist/devportal/components/LoadingState.js +5 -8
  22. package/dist/devportal/components/LoadingState.js.map +1 -1
  23. package/dist/devportal/components/PageLayout.d.ts.map +1 -1
  24. package/dist/devportal/components/PageLayout.js +10 -13
  25. package/dist/devportal/components/PageLayout.js.map +1 -1
  26. package/dist/devportal/components/PermissionGuard.d.ts.map +1 -1
  27. package/dist/devportal/components/PermissionGuard.js +11 -14
  28. package/dist/devportal/components/PermissionGuard.js.map +1 -1
  29. package/dist/devportal/components/SecretDisplay.d.ts.map +1 -1
  30. package/dist/devportal/components/SecretDisplay.js +24 -30
  31. package/dist/devportal/components/SecretDisplay.js.map +1 -1
  32. package/dist/devportal/components/SectionNav.d.ts.map +1 -1
  33. package/dist/devportal/components/SectionNav.js +24 -27
  34. package/dist/devportal/components/SectionNav.js.map +1 -1
  35. package/dist/devportal/components/StatusBadge.d.ts.map +1 -1
  36. package/dist/devportal/components/StatusBadge.js +24 -33
  37. package/dist/devportal/components/StatusBadge.js.map +1 -1
  38. package/dist/devportal/index.d.ts +1 -0
  39. package/dist/devportal/index.d.ts.map +1 -1
  40. package/dist/devportal/index.js +1 -0
  41. package/dist/devportal/pages/AdminReviewPage.js +172 -172
  42. package/dist/devportal/pages/AnalyticsDashboardPage.js +43 -43
  43. package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
  44. package/dist/devportal/pages/ApiKeysPage.js +119 -119
  45. package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
  46. package/dist/devportal/pages/AppLogsPage.js +45 -45
  47. package/dist/devportal/pages/ApplicationDetailPage.js +607 -607
  48. package/dist/devportal/pages/ApplicationVersionsPage.js +13 -13
  49. package/dist/devportal/pages/ApplicationsPage.d.ts.map +1 -1
  50. package/dist/devportal/pages/ApplicationsPage.js +104 -101
  51. package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
  52. package/dist/devportal/pages/CreateApiKeyPage.js +45 -45
  53. package/dist/devportal/pages/CreateApplicationPage.js +38 -38
  54. package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
  55. package/dist/devportal/pages/CreateOAuthClientPage.js +52 -52
  56. package/dist/devportal/pages/CreateOrganizationPage.js +26 -26
  57. package/dist/devportal/pages/CreateWebhookPage.d.ts.map +1 -1
  58. package/dist/devportal/pages/CreateWebhookPage.js +48 -48
  59. package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
  60. package/dist/devportal/pages/DashboardPage.d.ts.map +1 -1
  61. package/dist/devportal/pages/DashboardPage.js +66 -72
  62. package/dist/devportal/pages/DashboardPage.js.map +1 -1
  63. package/dist/devportal/pages/DelegationsPage.js +174 -174
  64. package/dist/devportal/pages/DocumentationPage.js +93 -93
  65. package/dist/devportal/pages/EditApplicationPage.js +14 -14
  66. package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
  67. package/dist/devportal/pages/ListingPreviewPage.js +76 -76
  68. package/dist/devportal/pages/OAuthClientDetailPage.js +75 -75
  69. package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
  70. package/dist/devportal/pages/OAuthClientsPage.js +64 -64
  71. package/dist/devportal/pages/OrganizationDetailPage.js +189 -189
  72. package/dist/devportal/pages/OrganizationDetailPage.js.map +1 -1
  73. package/dist/devportal/pages/OrganizationMembersPage.js +10 -10
  74. package/dist/devportal/pages/OrganizationsPage.d.ts.map +1 -1
  75. package/dist/devportal/pages/OrganizationsPage.js +163 -154
  76. package/dist/devportal/pages/OrganizationsPage.js.map +1 -1
  77. package/dist/devportal/pages/PublishPage.js +104 -104
  78. package/dist/devportal/pages/PublishPage.js.map +1 -1
  79. package/dist/devportal/pages/PublisherProfilePage.js +55 -55
  80. package/dist/devportal/pages/ResourcesPage.js +60 -60
  81. package/dist/devportal/pages/RevenueDashboardPage.js +60 -60
  82. package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -1
  83. package/dist/devportal/pages/SellerDashboardPage.js +83 -83
  84. package/dist/devportal/pages/SellerItemsPage.js +163 -163
  85. package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
  86. package/dist/devportal/pages/SellerOrdersPage.js +63 -63
  87. package/dist/devportal/pages/SettingsPage.js +21 -21
  88. package/dist/devportal/pages/SetupPage.js +183 -183
  89. package/dist/devportal/pages/SetupPage.js.map +1 -1
  90. package/dist/devportal/pages/StoreSubmissionPage.js +5 -5
  91. package/dist/devportal/pages/WebhookDetailPage.js +123 -123
  92. package/dist/devportal/pages/WebhookDetailPage.js.map +1 -1
  93. package/dist/devportal/pages/WebhooksPage.js +118 -118
  94. package/dist/devportal/pages/WebhooksPage.js.map +1 -1
  95. package/dist/devportal/utils/i18n.d.ts +9 -2
  96. package/dist/devportal/utils/i18n.d.ts.map +1 -1
  97. package/dist/devportal/utils/i18n.js +13 -10
  98. package/dist/devportal/utils/i18n.js.map +1 -1
  99. package/dist/index.js +2 -1
  100. package/dist/translations/en.d.ts +1251 -0
  101. package/dist/translations/en.d.ts.map +1 -0
  102. package/dist/translations/en.js +1235 -0
  103. package/dist/translations/en.js.map +1 -0
  104. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationsPage.js","names":[],"sources":["../../../src/devportal/pages/ApplicationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n AppWindow,\n Loader2,\n AlertCircle,\n Archive,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { AppCard } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, AppStatus, AppType } from \"../types\";\nimport { APP_TYPE_LABELS, APP_STATUS_COLORS } from \"../utils/constants\";\nimport {\n useGetApplicationsQuery,\n useDeactivateApplicationMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\ntype BulkAction = \"deactivate\" | null;\n\nfunction formatBulkResult(\n action: \"Deactivated\",\n succeeded: number,\n failed: number,\n): string {\n return `${action} ${succeeded} application${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`;\n}\n\nfunction StatusBadge({ status }: { status: AppStatus }) {\n const colors: Record<string, string> = {\n ACTIVE: \"bg-status-success-bg-subtle text-status-success-text\",\n PUBLISHED: \"bg-status-success-bg-subtle text-status-success-text\",\n DRAFT: \"bg-bg-sunken text-text-secondary\",\n SUBMITTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n IN_REVIEW: \"bg-status-warning-bg-subtle text-status-warning-text\",\n APPROVED: \"bg-status-success-bg-subtle text-status-success-text\",\n UNLISTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n REJECTED: \"bg-status-error-bg-subtle text-status-error-text\",\n SUSPENDED: \"bg-status-error-bg-subtle text-status-error-text\",\n INACTIVE: \"bg-bg-sunken text-text-muted\",\n REVOKED: \"bg-status-error-bg-subtle text-status-error-text\",\n DEPRECATED: \"bg-bg-sunken text-text-muted\",\n };\n return (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${colors[status] ?? \"bg-bg-sunken text-text-secondary\"}`}\n >\n {status === \"INACTIVE\" || status === \"REVOKED\"\n ? \"Deactivated\"\n : status.replace(/_/g, \" \")}\n </span>\n );\n}\n\n/**\n * Map GraphQL application to local Application type\n */\nfunction mapApplication(app: ApplicationFieldsFragment): Application {\n // Convert screenshots array to Record if needed\n const screenshots = Array.isArray(app.screenshots)\n ? app.screenshots.length > 0\n ? (app.screenshots as unknown as Record<string, unknown>)\n : undefined\n : (app.screenshots as Record<string, unknown> | undefined);\n\n // Convert pricingDetails\n const pricingDetails =\n app.pricingDetails && typeof app.pricingDetails === \"object\"\n ? (app.pricingDetails as Record<string, unknown>)\n : undefined;\n\n return {\n id: app.id,\n name: app.name,\n slug: app.slug,\n description: app.description ?? undefined,\n shortDescription: app.shortDescription ?? undefined,\n appType: app.appType as AppType,\n status: app.status as AppStatus,\n visibility: app.visibility as \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\",\n category: app.category ?? undefined,\n tags: app.tags ?? [],\n keywords: app.keywords ?? [],\n iconUrl: app.iconUrl ?? undefined,\n bannerUrl: app.bannerUrl ?? undefined,\n screenshots,\n videoUrl: app.videoUrl ?? undefined,\n websiteUrl: app.websiteUrl ?? undefined,\n documentationUrl: app.documentationUrl ?? undefined,\n supportUrl: app.supportUrl ?? undefined,\n privacyPolicyUrl: app.privacyPolicyUrl ?? undefined,\n termsOfServiceUrl: app.termsOfServiceUrl ?? undefined,\n githubUrl: app.githubUrl ?? undefined,\n pricingType: app.pricingType ?? undefined,\n pricingDetails,\n isVerified: app.isVerified,\n isFeatured: app.isFeatured,\n averageRating: app.averageRating ?? undefined,\n reviewCount: app.reviewCount ?? 0,\n installCount: app.installCount ?? 0,\n activeInstallCount: app.activeInstallCount ?? 0,\n requiresApproval: app.requiresApproval,\n reviewStatus: app.reviewStatus ?? undefined,\n reviewNotes: app.reviewNotes ?? undefined,\n reviewedBy: app.reviewedBy ?? undefined,\n lastReviewedAt: app.lastReviewedAt ?? undefined,\n submittedAt: app.submittedAt ?? undefined,\n publishedAt: app.publishedAt ?? undefined,\n developerId: app.developerId,\n developerOrganizationId: app.developerOrganizationId ?? undefined,\n tenantId: app.tenantId,\n createdAt: app.createdAt,\n updatedAt: app.updatedAt,\n };\n}\n\n/**\n * Applications listing page\n *\n * Displays all applications for the current developer.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const ApplicationsPage: FC = () => {\n const { navigate, basePath, activeOrgId } = 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 createPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_CREATE);\n const updatePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_UPDATE);\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"list\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<AppStatus | \"\">(\"\");\n const [typeFilter, setTypeFilter] = useState<AppType | \"\">(\"\");\n const [selectedAppIds, setSelectedAppIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<BulkAction>(null);\n const [feedback, setFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n\n // Fetch applications from GraphQL API — always scope to active org so\n // totalCount from the server matches what the user actually owns.\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: {\n limit: 50,\n offset: 0,\n status: statusFilter || undefined,\n organizationId: activeOrgId ?? undefined,\n },\n fetchPolicy: \"cache-and-network\",\n });\n\n const applications: Application[] = useMemo(\n () =>\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) =>\n mapApplication(edge.node),\n ) ?? [],\n [data?.applications?.edges],\n );\n\n const [deactivateApp] = useDeactivateApplicationMutation();\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Client-side filtering: org scope + search + type (status filtered server-side)\n const filteredApps = applications.filter((app: Application) => {\n const matchesOrg =\n !activeOrgId || app.developerOrganizationId === activeOrgId;\n const matchesSearch =\n !searchQuery ||\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n app.shortDescription?.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesType = !typeFilter || app.appType === typeFilter;\n return matchesOrg && matchesSearch && matchesType;\n });\n\n const appStats = useMemo(() => {\n // Use filteredApps (org-scoped) for all counts so stats match what's shown.\n const total = data?.applications?.totalCount ?? filteredApps.length;\n const active = filteredApps.filter(\n (app: Application) => app.status === \"ACTIVE\",\n ).length;\n const draft = filteredApps.filter(\n (app: Application) => app.status === \"DRAFT\",\n ).length;\n const published = filteredApps.filter(\n (app: Application) => app.status === \"PUBLISHED\",\n ).length;\n return { total, active, draft, published };\n }, [filteredApps, data?.applications?.totalCount]);\n\n const selectedApps = useMemo(\n () => applications.filter((app: Application) => selectedAppIds.has(app.id)),\n [applications, selectedAppIds],\n );\n\n const deactivatableApps = useMemo(\n () =>\n selectedApps.filter(\n (app: Application) =>\n app.status !== \"INACTIVE\" && app.status !== \"REVOKED\",\n ),\n [selectedApps],\n );\n\n const allVisibleSelected =\n filteredApps.length > 0 &&\n filteredApps.every((app: Application) => selectedAppIds.has(app.id));\n const partiallySelected =\n filteredApps.some((app: Application) => selectedAppIds.has(app.id)) &&\n !allVisibleSelected;\n\n const toggleAppSelection = (appId: string, checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(appId);\n } else {\n next.delete(appId);\n }\n return next;\n });\n };\n\n const toggleAllVisible = (checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n filteredApps.forEach((app: Application) => {\n if (checked) {\n next.add(app.id);\n } else {\n next.delete(app.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedAppIds(new Set());\n\n const runBulkDeactivate = async () => {\n if (deactivatableApps.length === 0) return;\n\n const results = await Promise.allSettled(\n deactivatableApps.map((app: Application) =>\n deactivateApp({ variables: { id: app.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result: PromiseSettledResult<unknown>) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: formatBulkResult(\"Deactivated\", succeeded, failed),\n });\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\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 <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingApplications\", \"Loading applications...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\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 <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\n \"devportal.failedToLoadApplications\",\n \"Failed to load applications\",\n )}\n </h3>\n <p className=\"mt-1 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.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n purpose={tr(\n \"devportal.applicationsPurpose\",\n \"Applications are the integrations you build on the platform — OAuth apps, store listings, and API consumers. Create one here to get client credentials, request scopes, and ship to the marketplace.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n disabled={!createPerm.allowed}\n aria-disabled={!createPerm.allowed}\n title={\n !createPerm.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-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors 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 <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\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.searchApplications\",\n \"Search applications...\",\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 {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as AppStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">\n {tr(\"devportal.allStatus\", \"All Status\")}\n </option>\n {Object.keys(APP_STATUS_COLORS).map((status) => (\n <option key={status} value={status}>\n {status.replace(/_/g, \" \")}\n </option>\n ))}\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as AppType | \"\")}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">{tr(\"devportal.allTypes\", \"All Types\")}</option>\n {Object.entries(APP_TYPE_LABELS).map(([value, label]) => (\n <option key={value} value={value}>\n {label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 rounded-md border p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 ${viewMode === \"grid\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title=\"Grid view\"\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 ${viewMode === \"list\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title=\"Table view\"\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel className=\"mt-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.active\", \"Active\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.published}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.draft\", \"Draft\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.draft}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {feedback && (\n <div\n className={`rounded-lg border px-4 py-3 text-sm ${\n feedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-status-error-border/40 bg-status-error-bg/10 text-status-error-text\"\n }`}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{feedback.message}</span>\n <button\n onClick={() => setFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {filteredApps.length > 0 && (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-pop)]\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel=\"Select all visible applications\"\n />\n <span className=\"text-sm font-medium\">\n Select all visible\n </span>\n </div>\n {selectedAppIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedAppIds.size} selected\n </span>\n <button\n onClick={() => setBulkAction(\"deactivate\")}\n disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n aria-disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n title={\n !updatePerm.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 border border-status-warning-bg px-3 py-2 text-sm font-medium text-status-warning-text transition-colors hover:bg-status-warning-bg-subtle disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Archive size={16} aria-hidden=\"true\" />\n {tr(\"devportal.deactivateSelected\", \"Deactivate Selected\")}\n {deactivatableApps.length > 0\n ? ` (${deactivatableApps.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredApps.length} application\n {filteredApps.length !== 1 ? \"s\" : \"\"} found\n </p>\n\n {/* Applications */}\n {filteredApps.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredApps.map((app: Application) => (\n <AppCard\n key={app.id}\n app={app}\n selected={selectedAppIds.has(app.id)}\n onToggleSelect={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n />\n ))}\n </div>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken text-left\">\n <th className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel=\"Select all\"\n />\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Name\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Type\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Created\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\"></th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {filteredApps.map((app: Application) => (\n <tr\n key={app.id}\n className={`hover:bg-bg-surface-hover transition-colors ${selectedAppIds.has(app.id) ? \"bg-action-primary-bg/5\" : \"\"}`}\n >\n <td className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={selectedAppIds.has(app.id)}\n onChange={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n ariaLabel={`Select ${app.name}`}\n />\n </td>\n <td className=\"px-4 py-3\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"group flex items-center gap-3 text-left\"\n >\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt=\"\"\n className=\"h-8 w-8 rounded object-cover\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded bg-bg-sunken text-text-secondary\">\n <AppWindow size={14} />\n </div>\n )}\n <div>\n <p className=\"font-medium text-text-primary group-hover:text-action-primary-text transition-colors\">\n {app.name}\n </p>\n {app.shortDescription && (\n <p className=\"text-xs text-text-secondary line-clamp-1\">\n {app.shortDescription}\n </p>\n )}\n </div>\n </button>\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge status={app.status} />\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {APP_TYPE_LABELS[app.appType] || app.appType}\n </td>\n <td className=\"px-4 py-3 text-text-secondary tabular-nums\">\n {new Date(app.createdAt).toLocaleDateString()}\n </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"rounded-md px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors\"\n >\n View\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n ) : (\n <EmptyState\n illustration={\n searchQuery || statusFilter || typeFilter\n ? \"empty-search\"\n : \"empty-data\"\n }\n icon={<AppWindow size={32} />}\n title={tr(\"devportal.noApplicationsFound\", \"No applications found\")}\n description={\n searchQuery || statusFilter || typeFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstApp\",\n \"Create your first application to get started\",\n )\n }\n action={\n !searchQuery && !statusFilter && !typeFilter ? (\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 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 <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n <ConfirmDialog\n open={bulkAction === \"deactivate\"}\n onOpenChange={(open) => {\n if (!open) setBulkAction(null);\n }}\n onConfirm={runBulkDeactivate}\n title=\"Deactivate selected applications\"\n description={`Deactivate ${deactivatableApps.length} selected application${deactivatableApps.length === 1 ? \"\" : \"s\"}? Users and integrations may lose access until these apps are re-enabled.`}\n confirmLabel=\"Deactivate\"\n cancelLabel=\"Cancel\"\n variant=\"warning\"\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ApplicationsPage.js","names":[],"sources":["../../../src/devportal/pages/ApplicationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n AppWindow,\n Loader2,\n AlertCircle,\n Archive,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { AppCard } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useTr } from \"../utils/i18n\";\nimport type { Application, AppStatus, AppType } from \"../types\";\nimport { APP_TYPE_LABELS, APP_STATUS_COLORS } from \"../utils/constants\";\nimport {\n useGetApplicationsQuery,\n useDeactivateApplicationMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\ntype BulkAction = \"deactivate\" | null;\n\nfunction formatBulkResult(\n action: string,\n succeeded: number,\n failed: number,\n): string {\n return `${action} ${succeeded} application${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`;\n}\n\nfunction StatusBadge({\n status,\n tr,\n}: {\n status: AppStatus;\n tr: (key: string, fallback: string) => string;\n}) {\n const colors: Record<string, string> = {\n ACTIVE: \"bg-status-success-bg-subtle text-status-success-text\",\n PUBLISHED: \"bg-status-success-bg-subtle text-status-success-text\",\n DRAFT: \"bg-bg-sunken text-text-secondary\",\n SUBMITTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n IN_REVIEW: \"bg-status-warning-bg-subtle text-status-warning-text\",\n APPROVED: \"bg-status-success-bg-subtle text-status-success-text\",\n UNLISTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n REJECTED: \"bg-status-error-bg-subtle text-status-error-text\",\n SUSPENDED: \"bg-status-error-bg-subtle text-status-error-text\",\n INACTIVE: \"bg-bg-sunken text-text-muted\",\n REVOKED: \"bg-status-error-bg-subtle text-status-error-text\",\n DEPRECATED: \"bg-bg-sunken text-text-muted\",\n };\n return (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${colors[status] ?? \"bg-bg-sunken text-text-secondary\"}`}\n >\n {status === \"INACTIVE\" || status === \"REVOKED\"\n ? tr(\"devportal.status.deactivated\", \"Deactivated\")\n : status.replace(/_/g, \" \")}\n </span>\n );\n}\n\n/**\n * Map GraphQL application to local Application type\n */\nfunction mapApplication(app: ApplicationFieldsFragment): Application {\n // Convert screenshots array to Record if needed\n const screenshots = Array.isArray(app.screenshots)\n ? app.screenshots.length > 0\n ? (app.screenshots as unknown as Record<string, unknown>)\n : undefined\n : (app.screenshots as Record<string, unknown> | undefined);\n\n // Convert pricingDetails\n const pricingDetails =\n app.pricingDetails && typeof app.pricingDetails === \"object\"\n ? (app.pricingDetails as Record<string, unknown>)\n : undefined;\n\n return {\n id: app.id,\n name: app.name,\n slug: app.slug,\n description: app.description ?? undefined,\n shortDescription: app.shortDescription ?? undefined,\n appType: app.appType as AppType,\n status: app.status as AppStatus,\n visibility: app.visibility as \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\",\n category: app.category ?? undefined,\n tags: app.tags ?? [],\n keywords: app.keywords ?? [],\n iconUrl: app.iconUrl ?? undefined,\n bannerUrl: app.bannerUrl ?? undefined,\n screenshots,\n videoUrl: app.videoUrl ?? undefined,\n websiteUrl: app.websiteUrl ?? undefined,\n documentationUrl: app.documentationUrl ?? undefined,\n supportUrl: app.supportUrl ?? undefined,\n privacyPolicyUrl: app.privacyPolicyUrl ?? undefined,\n termsOfServiceUrl: app.termsOfServiceUrl ?? undefined,\n githubUrl: app.githubUrl ?? undefined,\n pricingType: app.pricingType ?? undefined,\n pricingDetails,\n isVerified: app.isVerified,\n isFeatured: app.isFeatured,\n averageRating: app.averageRating ?? undefined,\n reviewCount: app.reviewCount ?? 0,\n installCount: app.installCount ?? 0,\n activeInstallCount: app.activeInstallCount ?? 0,\n requiresApproval: app.requiresApproval,\n reviewStatus: app.reviewStatus ?? undefined,\n reviewNotes: app.reviewNotes ?? undefined,\n reviewedBy: app.reviewedBy ?? undefined,\n lastReviewedAt: app.lastReviewedAt ?? undefined,\n submittedAt: app.submittedAt ?? undefined,\n publishedAt: app.publishedAt ?? undefined,\n developerId: app.developerId,\n developerOrganizationId: app.developerOrganizationId ?? undefined,\n tenantId: app.tenantId,\n createdAt: app.createdAt,\n updatedAt: app.updatedAt,\n };\n}\n\n/**\n * Applications listing page\n *\n * Displays all applications for the current developer.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const ApplicationsPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const tr = useTr();\n const createPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_CREATE);\n const updatePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_UPDATE);\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"list\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<AppStatus | \"\">(\"\");\n const [typeFilter, setTypeFilter] = useState<AppType | \"\">(\"\");\n const [selectedAppIds, setSelectedAppIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<BulkAction>(null);\n const [feedback, setFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n\n // Fetch applications from GraphQL API — always scope to active org so\n // totalCount from the server matches what the user actually owns.\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: {\n limit: 50,\n offset: 0,\n status: statusFilter || undefined,\n organizationId: activeOrgId ?? undefined,\n },\n fetchPolicy: \"cache-and-network\",\n });\n\n const applications: Application[] = useMemo(\n () =>\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) =>\n mapApplication(edge.node),\n ) ?? [],\n [data?.applications?.edges],\n );\n\n const [deactivateApp] = useDeactivateApplicationMutation();\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Client-side filtering: org scope + search + type (status filtered server-side)\n const filteredApps = applications.filter((app: Application) => {\n const matchesOrg =\n !activeOrgId || app.developerOrganizationId === activeOrgId;\n const matchesSearch =\n !searchQuery ||\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n app.shortDescription?.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesType = !typeFilter || app.appType === typeFilter;\n return matchesOrg && matchesSearch && matchesType;\n });\n\n const appStats = useMemo(() => {\n // Use filteredApps (org-scoped) for all counts so stats match what's shown.\n const total = data?.applications?.totalCount ?? filteredApps.length;\n const active = filteredApps.filter(\n (app: Application) => app.status === \"ACTIVE\",\n ).length;\n const draft = filteredApps.filter(\n (app: Application) => app.status === \"DRAFT\",\n ).length;\n const published = filteredApps.filter(\n (app: Application) => app.status === \"PUBLISHED\",\n ).length;\n return { total, active, draft, published };\n }, [filteredApps, data?.applications?.totalCount]);\n\n const selectedApps = useMemo(\n () => applications.filter((app: Application) => selectedAppIds.has(app.id)),\n [applications, selectedAppIds],\n );\n\n const deactivatableApps = useMemo(\n () =>\n selectedApps.filter(\n (app: Application) =>\n app.status !== \"INACTIVE\" && app.status !== \"REVOKED\",\n ),\n [selectedApps],\n );\n\n const allVisibleSelected =\n filteredApps.length > 0 &&\n filteredApps.every((app: Application) => selectedAppIds.has(app.id));\n const partiallySelected =\n filteredApps.some((app: Application) => selectedAppIds.has(app.id)) &&\n !allVisibleSelected;\n\n const toggleAppSelection = (appId: string, checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(appId);\n } else {\n next.delete(appId);\n }\n return next;\n });\n };\n\n const toggleAllVisible = (checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n filteredApps.forEach((app: Application) => {\n if (checked) {\n next.add(app.id);\n } else {\n next.delete(app.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedAppIds(new Set());\n\n const runBulkDeactivate = async () => {\n if (deactivatableApps.length === 0) return;\n\n const results = await Promise.allSettled(\n deactivatableApps.map((app: Application) =>\n deactivateApp({ variables: { id: app.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result: PromiseSettledResult<unknown>) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: formatBulkResult(\n tr(\"devportal.deactivated\", \"Deactivated\"),\n succeeded,\n failed,\n ),\n });\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\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 <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingApplications\", \"Loading applications...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\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 <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\n \"devportal.failedToLoadApplications\",\n \"Failed to load applications\",\n )}\n </h3>\n <p className=\"mt-1 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.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n purpose={tr(\n \"devportal.applicationsPurpose\",\n \"Applications are the integrations you build on the platform — OAuth apps, store listings, and API consumers. Create one here to get client credentials, request scopes, and ship to the marketplace.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n disabled={!createPerm.allowed}\n aria-disabled={!createPerm.allowed}\n title={\n !createPerm.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-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors 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 <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\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.searchApplications\",\n \"Search applications...\",\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 {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as AppStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">\n {tr(\"devportal.allStatus\", \"All Status\")}\n </option>\n {Object.keys(APP_STATUS_COLORS).map((status) => (\n <option key={status} value={status}>\n {tr(\n `devportal.appStatus.${status}`,\n status.replace(/_/g, \" \"),\n )}\n </option>\n ))}\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as AppType | \"\")}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">{tr(\"devportal.allTypes\", \"All Types\")}</option>\n {Object.entries(APP_TYPE_LABELS).map(([value, label]) => (\n <option key={value} value={value}>\n {label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 rounded-md border p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 ${viewMode === \"grid\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title={tr(\"devportal.gridView\", \"Grid view\")}\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 ${viewMode === \"list\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title={tr(\"devportal.tableView\", \"Table view\")}\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel className=\"mt-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.active\", \"Active\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.published}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.draft\", \"Draft\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.draft}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {feedback && (\n <div\n className={`rounded-lg border px-4 py-3 text-sm ${\n feedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-status-error-border/40 bg-status-error-bg/10 text-status-error-text\"\n }`}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{feedback.message}</span>\n <button\n onClick={() => setFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n {tr(\"devportal.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {filteredApps.length > 0 && (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-pop)]\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel={tr(\n \"devportal.selectAllVisibleApplications\",\n \"Select all visible applications\",\n )}\n />\n <span className=\"text-sm font-medium\">\n {tr(\"devportal.selectAllVisible\", \"Select all visible\")}\n </span>\n </div>\n {selectedAppIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr(\"devportal.clearSelection\", \"Clear selection\")}\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedAppIds.size}{\" \"}\n {tr(\"devportal.selectedLower\", \"selected\")}\n </span>\n <button\n onClick={() => setBulkAction(\"deactivate\")}\n disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n aria-disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n title={\n !updatePerm.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 border border-status-warning-bg px-3 py-2 text-sm font-medium text-status-warning-text transition-colors hover:bg-status-warning-bg-subtle disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Archive size={16} aria-hidden=\"true\" />\n {tr(\"devportal.deactivateSelected\", \"Deactivate Selected\")}\n {deactivatableApps.length > 0\n ? ` (${deactivatableApps.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredApps.length}{\" \"}\n {filteredApps.length === 1\n ? tr(\"devportal.applicationFound\", \"application found\")\n : tr(\"devportal.applicationsFound\", \"applications found\")}\n </p>\n\n {/* Applications */}\n {filteredApps.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredApps.map((app: Application) => (\n <AppCard\n key={app.id}\n app={app}\n selected={selectedAppIds.has(app.id)}\n onToggleSelect={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n />\n ))}\n </div>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken text-left\">\n <th className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel={tr(\"devportal.selectAll\", \"Select all\")}\n />\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n {tr(\"devportal.name\", \"Name\")}\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n {tr(\"devportal.status\", \"Status\")}\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n {tr(\"devportal.type\", \"Type\")}\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n {tr(\"devportal.created\", \"Created\")}\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\"></th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {filteredApps.map((app: Application) => (\n <tr\n key={app.id}\n className={`hover:bg-bg-surface-hover transition-colors ${selectedAppIds.has(app.id) ? \"bg-action-primary-bg/5\" : \"\"}`}\n >\n <td className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={selectedAppIds.has(app.id)}\n onChange={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n ariaLabel={tr(\n \"devportal.selectNamed\",\n \"Select {{name}}\",\n { name: app.name },\n )}\n />\n </td>\n <td className=\"px-4 py-3\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"group flex items-center gap-3 text-left\"\n >\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt=\"\"\n className=\"h-8 w-8 rounded object-cover\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded bg-bg-sunken text-text-secondary\">\n <AppWindow size={14} />\n </div>\n )}\n <div>\n <p className=\"font-medium text-text-primary group-hover:text-action-primary-text transition-colors\">\n {app.name}\n </p>\n {app.shortDescription && (\n <p className=\"text-xs text-text-secondary line-clamp-1\">\n {app.shortDescription}\n </p>\n )}\n </div>\n </button>\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge status={app.status} tr={tr} />\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {APP_TYPE_LABELS[app.appType] || app.appType}\n </td>\n <td className=\"px-4 py-3 text-text-secondary tabular-nums\">\n {new Date(app.createdAt).toLocaleDateString()}\n </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"rounded-md px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.view\", \"View\")}\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n ) : (\n <EmptyState\n illustration={\n searchQuery || statusFilter || typeFilter\n ? \"empty-search\"\n : \"empty-data\"\n }\n icon={<AppWindow size={32} />}\n title={tr(\"devportal.noApplicationsFound\", \"No applications found\")}\n description={\n searchQuery || statusFilter || typeFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstApp\",\n \"Create your first application to get started\",\n )\n }\n action={\n !searchQuery && !statusFilter && !typeFilter ? (\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 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 <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n <ConfirmDialog\n open={bulkAction === \"deactivate\"}\n onOpenChange={(open) => {\n if (!open) setBulkAction(null);\n }}\n onConfirm={runBulkDeactivate}\n title={tr(\n \"devportal.deactivateSelectedApplications\",\n \"Deactivate selected applications\",\n )}\n description={tr(\n \"devportal.deactivateSelectedApplicationsDescription\",\n \"Deactivate {{count}} selected application(s)? Users and integrations may lose access until these apps are re-enabled.\",\n { count: deactivatableApps.length },\n )}\n confirmLabel={tr(\"devportal.deactivate\", \"Deactivate\")}\n cancelLabel={tr(\"devportal.cancel\", \"Cancel\")}\n variant=\"warning\"\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
@@ -10,9 +10,9 @@ import "../components/index.js";
10
10
  import "../hooks/index.js";
11
11
  import { executeRecaptcha as f } from "../utils/recaptcha.js";
12
12
  import { useState as p } from "react";
13
- import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
- import { Loader2 as h, Save as g, X as _ } from "lucide-react";
15
- import { jsx as v, jsxs as y } from "react/jsx-runtime";
13
+ import { Loader2 as m, Save as h, X as g } from "lucide-react";
14
+ import { jsx as _, jsxs as v } from "react/jsx-runtime";
15
+ import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
16
16
  import { OptionalFields as b } from "@burdenoff/fe-libs/ui";
17
17
  //#region src/devportal/pages/CreateApiKeyPage.tsx
18
18
  var x = {
@@ -28,7 +28,7 @@ function S(e) {
28
28
  if (t != null) return new Date(Date.now() + t).toISOString();
29
29
  }
30
30
  var C = () => {
31
- let { t: x } = m(), C = (e, t) => {
31
+ let { t: x } = y(), C = (e, t) => {
32
32
  let n = x(e);
33
33
  return n === e ? t : n;
34
34
  }, { navigate: w, basePath: T } = e(), [E, D] = p(null), [O, k] = p({}), [A, j] = p(null), [M, { loading: N }] = r(), P = t(), { data: F, loading: I } = n({ fetchPolicy: "cache-and-network" }), L = F?.currentUserApps?.apps.map((e) => ({
@@ -81,57 +81,57 @@ var C = () => {
81
81
  }, G = () => {
82
82
  B("/api-keys");
83
83
  };
84
- return E ? /* @__PURE__ */ v(i, {
84
+ return E ? /* @__PURE__ */ _(i, {
85
85
  title: "API Key Generated",
86
86
  description: "Your new API key has been generated successfully",
87
- children: /* @__PURE__ */ y("div", {
87
+ children: /* @__PURE__ */ v("div", {
88
88
  className: "max-w-2xl space-y-6",
89
89
  children: [
90
- /* @__PURE__ */ v(o, {
90
+ /* @__PURE__ */ _(o, {
91
91
  title: "API Key & Secret",
92
92
  secret: E.apiSecret,
93
93
  description: "Store this secret securely — you won't be able to see it again.",
94
94
  onDismiss: G
95
95
  }),
96
- /* @__PURE__ */ v(u, {
96
+ /* @__PURE__ */ _(u, {
97
97
  variant: "info",
98
98
  title: "Usage Instructions",
99
- children: /* @__PURE__ */ y("div", {
99
+ children: /* @__PURE__ */ v("div", {
100
100
  className: "mt-2 space-y-2",
101
101
  children: [
102
- /* @__PURE__ */ v("p", { children: "Your API key details:" }),
103
- /* @__PURE__ */ y("div", {
102
+ /* @__PURE__ */ _("p", { children: "Your API key details:" }),
103
+ /* @__PURE__ */ v("div", {
104
104
  className: "space-y-1",
105
- children: [/* @__PURE__ */ y("div", {
105
+ children: [/* @__PURE__ */ v("div", {
106
106
  className: "flex items-center gap-2",
107
- children: [/* @__PURE__ */ v("span", {
107
+ children: [/* @__PURE__ */ _("span", {
108
108
  className: "text-xs font-medium text-text-secondary",
109
109
  children: "Key ID:"
110
- }), /* @__PURE__ */ v("code", {
110
+ }), /* @__PURE__ */ _("code", {
111
111
  className: "rounded bg-bg-sunken px-2 py-0.5 text-sm",
112
112
  children: E.keyId
113
113
  })]
114
- }), /* @__PURE__ */ y("div", {
114
+ }), /* @__PURE__ */ v("div", {
115
115
  className: "flex items-center gap-2",
116
- children: [/* @__PURE__ */ v("span", {
116
+ children: [/* @__PURE__ */ _("span", {
117
117
  className: "text-xs font-medium text-text-secondary",
118
118
  children: "API Key:"
119
- }), /* @__PURE__ */ v("code", {
119
+ }), /* @__PURE__ */ _("code", {
120
120
  className: "rounded bg-bg-sunken px-2 py-0.5 text-sm",
121
121
  children: E.apiKey
122
122
  })]
123
123
  })]
124
124
  }),
125
- /* @__PURE__ */ v("p", {
125
+ /* @__PURE__ */ _("p", {
126
126
  className: "text-xs text-text-secondary mt-2",
127
127
  children: "Use the API key and secret to authenticate your requests."
128
128
  })
129
129
  ]
130
130
  })
131
131
  }),
132
- /* @__PURE__ */ v("div", {
132
+ /* @__PURE__ */ _("div", {
133
133
  className: "flex gap-3",
134
- children: /* @__PURE__ */ v("button", {
134
+ children: /* @__PURE__ */ _("button", {
135
135
  onClick: G,
136
136
  className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
137
137
  children: "Continue to API Keys"
@@ -139,49 +139,49 @@ var C = () => {
139
139
  })
140
140
  ]
141
141
  })
142
- }) : /* @__PURE__ */ v(d, {
142
+ }) : /* @__PURE__ */ _(d, {
143
143
  authOnly: !0,
144
- children: /* @__PURE__ */ v(i, {
144
+ children: /* @__PURE__ */ _(i, {
145
145
  title: "Generate API Key",
146
146
  description: "Generate a new API key for programmatic access",
147
147
  purpose: "Create a key your backend or scripts can use to call the API without a logged-in user. Scope it to one app and the minimum permissions it needs — the secret is shown only once at creation, so copy it somewhere safe.",
148
148
  backLink: "/api-keys",
149
149
  backLabel: "Back to API Keys",
150
- children: /* @__PURE__ */ y("form", {
150
+ children: /* @__PURE__ */ v("form", {
151
151
  onSubmit: W,
152
152
  className: "max-w-3xl space-y-8",
153
153
  children: [
154
- A && /* @__PURE__ */ v(u, {
154
+ A && /* @__PURE__ */ _(u, {
155
155
  variant: "error",
156
156
  title: "Error Generating API Key",
157
157
  onDismiss: () => j(null),
158
158
  children: A
159
159
  }),
160
- /* @__PURE__ */ v(a, {
160
+ /* @__PURE__ */ _(a, {
161
161
  title: "Basic Information",
162
162
  description: "Configure the API key settings",
163
- children: /* @__PURE__ */ y("div", {
163
+ children: /* @__PURE__ */ v("div", {
164
164
  className: "space-y-4",
165
- children: [I ? /* @__PURE__ */ y("div", {
165
+ children: [I ? /* @__PURE__ */ v("div", {
166
166
  className: "flex items-center gap-2 py-2",
167
- children: [/* @__PURE__ */ v(h, { className: "h-4 w-4 animate-spin text-text-secondary" }), /* @__PURE__ */ v("span", {
167
+ children: [/* @__PURE__ */ _(m, { className: "h-4 w-4 animate-spin text-text-secondary" }), /* @__PURE__ */ _("span", {
168
168
  className: "text-sm text-text-secondary",
169
169
  children: "Loading apps..."
170
170
  })]
171
- }) : L.length === 0 ? /* @__PURE__ */ v(u, {
171
+ }) : L.length === 0 ? /* @__PURE__ */ _(u, {
172
172
  variant: "warning",
173
173
  title: "No Apps Found",
174
- children: /* @__PURE__ */ y("p", { children: [
174
+ children: /* @__PURE__ */ v("p", { children: [
175
175
  "You need to create an OAuth app first before generating API keys.",
176
176
  " ",
177
- /* @__PURE__ */ v("button", {
177
+ /* @__PURE__ */ _("button", {
178
178
  type: "button",
179
179
  onClick: () => B("/oauth/new"),
180
180
  className: "text-primary underline hover:no-underline",
181
181
  children: "Create an app"
182
182
  })
183
183
  ] })
184
- }) : /* @__PURE__ */ v(c, {
184
+ }) : /* @__PURE__ */ _(c, {
185
185
  label: "App",
186
186
  value: R.appId,
187
187
  onChange: (e) => H("appId", e.target.value),
@@ -193,7 +193,7 @@ var C = () => {
193
193
  error: O.appId,
194
194
  required: !0,
195
195
  hint: "The OAuth app this API key will be associated with"
196
- }), /* @__PURE__ */ v(s, {
196
+ }), /* @__PURE__ */ _(s, {
197
197
  label: "Key Name",
198
198
  placeholder: "e.g., Production Backend Key",
199
199
  value: R.name,
@@ -204,12 +204,12 @@ var C = () => {
204
204
  })]
205
205
  })
206
206
  }),
207
- /* @__PURE__ */ v(a, {
207
+ /* @__PURE__ */ _(a, {
208
208
  title: "Scopes",
209
209
  description: "Define what this API key can access",
210
- children: /* @__PURE__ */ v("div", {
210
+ children: /* @__PURE__ */ _("div", {
211
211
  className: "space-y-4",
212
- children: /* @__PURE__ */ v(l, {
212
+ children: /* @__PURE__ */ _(l, {
213
213
  label: "Scopes",
214
214
  value: R.scopes,
215
215
  onChange: (e) => H("scopes", e),
@@ -219,13 +219,13 @@ var C = () => {
219
219
  })
220
220
  })
221
221
  }),
222
- /* @__PURE__ */ v(b, {
222
+ /* @__PURE__ */ _(b, {
223
223
  title: "Advanced settings",
224
224
  description: "Rate-limit overrides, allowed origins, scope tuning, and expiration.",
225
- children: /* @__PURE__ */ y(a, {
225
+ children: /* @__PURE__ */ v(a, {
226
226
  title: "Key Expiration",
227
227
  description: "Set when this key should expire",
228
- children: [/* @__PURE__ */ v(c, {
228
+ children: [/* @__PURE__ */ _(c, {
229
229
  label: "Expires In",
230
230
  value: R.expiresIn,
231
231
  onChange: (e) => H("expiresIn", e.target.value),
@@ -256,25 +256,25 @@ var C = () => {
256
256
  }
257
257
  ],
258
258
  hint: "Keys can be revoked at any time regardless of expiration"
259
- }), R.expiresIn === "never" && /* @__PURE__ */ v(u, {
259
+ }), R.expiresIn === "never" && /* @__PURE__ */ _(u, {
260
260
  variant: "warning",
261
261
  className: "mt-4",
262
262
  children: "Non-expiring keys are a security risk. Consider setting an expiration and rotating keys periodically."
263
263
  })]
264
264
  })
265
265
  }),
266
- /* @__PURE__ */ y("div", {
266
+ /* @__PURE__ */ v("div", {
267
267
  className: "flex items-center justify-end gap-3 border-t pt-6",
268
- children: [/* @__PURE__ */ y("button", {
268
+ children: [/* @__PURE__ */ v("button", {
269
269
  type: "button",
270
270
  onClick: V,
271
271
  className: "inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors",
272
- children: [/* @__PURE__ */ v(_, { size: 16 }), "Cancel"]
273
- }), /* @__PURE__ */ y("button", {
272
+ children: [/* @__PURE__ */ _(g, { size: 16 }), "Cancel"]
273
+ }), /* @__PURE__ */ v("button", {
274
274
  type: "submit",
275
275
  disabled: N || L.length === 0,
276
276
  className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50",
277
- children: [/* @__PURE__ */ v(g, { size: 16 }), N ? "Generating..." : "Generate API Key"]
277
+ children: [/* @__PURE__ */ _(h, { size: 16 }), N ? "Generating..." : "Generate API Key"]
278
278
  })]
279
279
  })
280
280
  ]