@burdenoff/microfe-devportal 2026.609.3 → 2026.611.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/DevportalRoot.js +6 -1
- package/dist/devportal/DevportalRoot.js.map +1 -1
- package/dist/devportal/DevportalRoutes.js +169 -158
- package/dist/devportal/DevportalRoutes.js.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.js +120 -0
- package/dist/devportal/hooks/usePackageUpload.js.map +1 -0
- package/dist/devportal/pages/AnalyticsDashboardPage.js +73 -62
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +89 -82
- package/dist/devportal/pages/AppLogsPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +948 -367
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationVersionsPage.js +56 -45
- package/dist/devportal/pages/ApplicationVersionsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +30 -32
- package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js +8 -3
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +4 -4
- package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js +233 -228
- package/dist/devportal/pages/CreateOrganizationPage.js.map +1 -1
- package/dist/devportal/pages/CreateWebhookPage.js +159 -148
- package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
- package/dist/devportal/pages/DelegationsPage.js +1 -1
- package/dist/devportal/pages/DelegationsPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +25 -17
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/ListingPreviewPage.js +159 -153
- package/dist/devportal/pages/ListingPreviewPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +2 -2
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationDetailPage.js +158 -153
- package/dist/devportal/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationMembersPage.js +223 -212
- package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
- package/dist/devportal/pages/PublishPage.js +172 -165
- package/dist/devportal/pages/PublishPage.js.map +1 -1
- package/dist/devportal/pages/ResourcesPage.js.map +1 -1
- package/dist/devportal/pages/RevenueDashboardPage.js +405 -0
- package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -0
- package/dist/devportal/pages/SetupPage.js +170 -217
- package/dist/devportal/pages/SetupPage.js.map +1 -1
- package/dist/devportal/pages/StoreSubmissionPage.js +76 -97
- package/dist/devportal/pages/StoreSubmissionPage.js.map +1 -1
- package/dist/devportal/pages/WebhookDetailPage.js +172 -154
- package/dist/devportal/pages/WebhookDetailPage.js.map +1 -1
- package/dist/devportal/pages/WebhooksPage.js +218 -200
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/dist/devportal/providers/DevportalProvider.js +16 -13
- package/dist/devportal/providers/DevportalProvider.js.map +1 -1
- package/dist/devportal/types/index.js.map +1 -1
- package/dist/generated/global-operations.js +310 -225
- package/dist/generated/global-operations.js.map +1 -1
- package/package.json +1 -1
- package/dist/devportal/pages/RevenueRedirectPage.js +0 -14
- package/dist/devportal/pages/RevenueRedirectPage.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DelegationsPage.js","names":[],"sources":["../../../src/devportal/pages/DelegationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useCallback, useEffect, useMemo } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Shield,\n Users,\n Clock,\n XCircle,\n AlertTriangle,\n Loader2,\n AlertCircle,\n Bot,\n Zap,\n Cog,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { LoadingState } from \"../components/LoadingState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { cn } from \"../utils/cn\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { AppUserDelegation, DelegationStatus } from \"../types\";\nimport {\n useCurrentUserDelegationsQuery,\n useCurrentUserGrantedDelegationsQuery,\n useRevokeDelegationMutation,\n} from \"../../generated/global-operations\";\n\n// =============================================================================\n// HELPERS\n// =============================================================================\n\ntype ActiveTab = \"acting-as-me\" | \"granted-by-me\";\n\n/**\n * Map a GraphQL delegation fragment to the local AppUserDelegation type\n */\nfunction mapDelegation(d: {\n id: string;\n appId: string;\n userId: string;\n scopes: string[];\n workspaceIds?: string[] | null;\n status: string;\n grantedAt: string;\n grantedBy: string;\n expiresAt?: string | null;\n revokedAt?: string | null;\n revokedBy?: string | null;\n app: {\n id: string;\n name: string;\n description?: string | null;\n type: string;\n status: string;\n };\n}): AppUserDelegation {\n return {\n id: d.id,\n appId: d.appId,\n userId: d.userId,\n scopes: d.scopes,\n workspaceIds: d.workspaceIds ?? [],\n status: d.status as DelegationStatus,\n grantedAt: d.grantedAt,\n grantedBy: d.grantedBy,\n expiresAt: d.expiresAt ?? undefined,\n revokedAt: d.revokedAt ?? undefined,\n revokedBy: d.revokedBy ?? undefined,\n app: {\n id: d.app.id,\n name: d.app.name,\n description: d.app.description ?? undefined,\n type: d.app.type,\n status: d.app.status,\n },\n };\n}\n\n/**\n * Format a date string to relative time (\"2 hours ago\", \"3 days ago\", etc.)\n */\nfunction formatRelativeTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffWeeks = Math.floor(diffDays / 7);\n const diffMonths = Math.floor(diffDays / 30);\n\n if (diffSeconds < 60) return \"just now\";\n if (diffMinutes < 60)\n return `${diffMinutes} minute${diffMinutes !== 1 ? \"s\" : \"\"} ago`;\n if (diffHours < 24)\n return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? \"s\" : \"\"} ago`;\n if (diffWeeks < 5)\n return `${diffWeeks} week${diffWeeks !== 1 ? \"s\" : \"\"} ago`;\n if (diffMonths < 12)\n return `${diffMonths} month${diffMonths !== 1 ? \"s\" : \"\"} ago`;\n\n return date.toLocaleDateString();\n}\n\n/**\n * Format a future date as relative time (\"in 3 days\", \"in 2 hours\", etc.)\n */\nfunction formatExpirationTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n\n if (diffMs <= 0) return \"expired\";\n\n const diffMinutes = Math.floor(diffMs / (1000 * 60));\n const diffHours = Math.floor(diffMs / (1000 * 60 * 60));\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n\n if (diffMinutes < 60)\n return `in ${diffMinutes} minute${diffMinutes !== 1 ? \"s\" : \"\"}`;\n if (diffHours < 24)\n return `in ${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"}`;\n return `in ${diffDays} day${diffDays !== 1 ? \"s\" : \"\"}`;\n}\n\n// =============================================================================\n// SUB-COMPONENTS\n// =============================================================================\n\n/**\n * Icon for app type (SERVICE, BOT, INTEGRATION, etc.)\n */\nconst AppTypeIcon: FC<{ type: string; className?: string }> = ({\n type,\n className,\n}) => {\n const iconProps = { className: cn(\"h-5 w-5\", className) };\n\n switch (type) {\n case \"SERVICE\":\n return <Cog {...iconProps} />;\n case \"BOT\":\n return <Bot {...iconProps} />;\n case \"INTEGRATION\":\n return <Zap {...iconProps} />;\n default:\n return <Shield {...iconProps} />;\n }\n};\n\n/**\n * Delegation status badge with color coding\n */\nconst DelegationStatusBadge: FC<{ status: DelegationStatus }> = ({\n status,\n}) => {\n const styles: Record<DelegationStatus, string> = {\n ACTIVE: \"bg-status-success-bg-subtle text-status-success-text\",\n REVOKED: \"bg-status-error-bg-subtle text-status-error-text\",\n EXPIRED: \"bg-bg-sunken text-text-primary\",\n };\n\n return (\n <span\n className={cn(\n \"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium\",\n styles[status],\n )}\n >\n {status}\n </span>\n );\n};\n\n/**\n * Success toast that auto-dismisses\n */\nconst SuccessToast: FC<{ message: string; onDismiss: () => void }> = ({\n message,\n onDismiss,\n}) => {\n useEffect(() => {\n const timer = setTimeout(onDismiss, 5000);\n return () => clearTimeout(timer);\n }, [onDismiss]);\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-status-success-bg-subtle p-4\">\n <div className=\"flex items-center gap-3\">\n <Shield className=\"h-5 w-5 text-status-success-text\" />\n <p className=\"text-sm font-medium text-status-success-text\">\n {message}\n </p>\n </div>\n </div>\n );\n};\n\n/**\n * Error banner\n */\nconst ErrorBanner: FC<{ message: string; onRetry?: () => void }> = ({\n message,\n onRetry,\n}) => {\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 return (\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-4\">\n <div className=\"flex items-center gap-3\">\n <AlertCircle className=\"h-5 w-5 text-destructive\" />\n <p className=\"flex-1 text-sm text-destructive\">{message}</p>\n {onRetry && (\n <button\n onClick={onRetry}\n className=\"rounded-md border px-3 py-1.5 text-xs font-medium hover:bg-muted transition-colors\"\n >\n {tr(\"devportal.retry\", \"Retry\")}\n </button>\n )}\n </div>\n </div>\n );\n};\n\n/**\n * Single delegation card for \"Apps Acting as Me\" tab\n */\nconst DelegationCard: FC<{\n delegation: AppUserDelegation;\n onRevoke: (delegation: AppUserDelegation) => void;\n isRevoking: boolean;\n selected: boolean;\n onToggleSelect: (checked: boolean) => void;\n canRevoke: boolean;\n}> = ({\n delegation,\n onRevoke,\n isRevoking,\n selected,\n onToggleSelect,\n canRevoke,\n}) => {\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 return (\n <div\n className={cn(\n \"rounded-lg border bg-card p-5 transition-colors hover:border-border/80\",\n selected && \"border-primary bg-primary/5\",\n delegation.status !== \"ACTIVE\" && \"opacity-60\",\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Left: App info */}\n <div className=\"flex items-start gap-4 min-w-0\">\n {delegation.status === \"ACTIVE\" && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={tr(\n \"devportal.selectDelegationAriaLabel\",\n `Select delegation ${delegation.app?.name ?? delegation.id}`,\n )}\n className=\"mt-1\"\n />\n </div>\n )}\n <div\n className={cn(\n \"flex h-12 w-12 shrink-0 items-center justify-center rounded-lg\",\n delegation.status === \"ACTIVE\"\n ? \"bg-primary/10 text-primary\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n <AppTypeIcon\n type={delegation.app?.type ?? \"SERVICE\"}\n className=\"h-6 w-6\"\n />\n </div>\n\n <div className=\"min-w-0 space-y-2\">\n {/* Name + status */}\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate font-semibold\">\n {delegation.app?.name ??\n tr(\"devportal.unknownApp\", \"Unknown App\")}\n </h3>\n <DelegationStatusBadge status={delegation.status} />\n </div>\n\n {/* Description */}\n {delegation.app?.description && (\n <p className=\"text-sm text-muted-foreground line-clamp-2\">\n {delegation.app.description}\n </p>\n )}\n\n {/* Metadata row */}\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.granted\", \"Granted\")}{\" \"}\n {formatRelativeTime(delegation.grantedAt)}\n </span>\n {delegation.expiresAt && (\n <span className=\"flex items-center gap-1\">\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.expires\", \"Expires\")}{\" \"}\n {formatExpirationTime(delegation.expiresAt)}\n </span>\n )}\n <span\n className={cn(\n \"rounded-md px-2 py-0.5 text-xs font-mono\",\n \"bg-muted text-muted-foreground\",\n )}\n >\n {delegation.app?.type ?? \"UNKNOWN\"}\n </span>\n </div>\n\n {/* Scopes */}\n {delegation.scopes.length > 0 && (\n <div>\n <p className=\"mb-1.5 text-xs text-muted-foreground\">\n {tr(\"devportal.permissions\", \"Permissions:\")}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {delegation.scopes.map((scope) => (\n <span\n key={scope}\n className=\"rounded-md bg-status-info-bg-subtle px-2 py-0.5 text-xs text-status-info-text\"\n >\n {scope}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Workspace count */}\n {delegation.workspaceIds.length > 0 && (\n <p className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n <Users className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.accessToWorkspaces\", \"Access to\")}{\" \"}\n {delegation.workspaceIds.length}{\" \"}\n {tr(\"devportal.workspace\", \"workspace\")}\n {delegation.workspaceIds.length !== 1\n ? tr(\"devportal.workspacePluralSuffix\", \"s\")\n : \"\"}\n </p>\n )}\n </div>\n </div>\n\n {/* Right: Revoke button (only for active delegations) */}\n {delegation.status === \"ACTIVE\" && (\n <button\n onClick={() => onRevoke(delegation)}\n disabled={isRevoking || !canRevoke}\n aria-disabled={!canRevoke}\n className=\"inline-flex shrink-0 items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isRevoking ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n {tr(\"devportal.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n );\n};\n\n/**\n * Single delegation card for \"Delegations I Granted\" tab\n */\nconst GrantedDelegationCard: FC<{\n delegation: AppUserDelegation;\n onRevoke: (delegation: AppUserDelegation) => void;\n isRevoking: boolean;\n selected: boolean;\n onToggleSelect: (checked: boolean) => void;\n canRevoke: boolean;\n}> = ({\n delegation,\n onRevoke,\n isRevoking,\n selected,\n onToggleSelect,\n canRevoke,\n}) => {\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 return (\n <div\n className={cn(\n \"rounded-lg border bg-card p-5 transition-colors hover:border-border/80\",\n selected && \"border-primary bg-primary/5\",\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Left: App info */}\n <div className=\"flex items-start gap-4 min-w-0\">\n {delegation.status === \"ACTIVE\" && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={tr(\n \"devportal.selectDelegationAriaLabel\",\n `Select delegation ${delegation.app?.name ?? delegation.id}`,\n )}\n className=\"mt-1\"\n />\n </div>\n )}\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-accent-purple-subtle text-accent-purple\">\n <AppTypeIcon\n type={delegation.app?.type ?? \"SERVICE\"}\n className=\"h-6 w-6\"\n />\n </div>\n\n <div className=\"min-w-0 space-y-2\">\n {/* Name + status */}\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate font-semibold\">\n {delegation.app?.name ??\n tr(\"devportal.unknownApp\", \"Unknown App\")}\n </h3>\n <DelegationStatusBadge status={delegation.status} />\n </div>\n\n {/* Target user */}\n <p className=\"text-sm text-muted-foreground\">\n {tr(\"devportal.grantedToUser\", \"Granted to user:\")}{\" \"}\n <code className=\"rounded bg-muted px-1.5 py-0.5 text-xs\">\n {delegation.userId}\n </code>\n </p>\n\n {/* Time */}\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.granted\", \"Granted\")}{\" \"}\n {formatRelativeTime(delegation.grantedAt)}\n </span>\n {delegation.expiresAt && (\n <span className=\"flex items-center gap-1\">\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.expires\", \"Expires\")}{\" \"}\n {new Date(delegation.expiresAt).toLocaleDateString()}\n </span>\n )}\n </div>\n\n {/* Scopes */}\n {delegation.scopes.length > 0 && (\n <div>\n <p className=\"mb-1.5 text-xs text-muted-foreground\">\n {tr(\"devportal.permissionsGranted\", \"Permissions granted:\")}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {delegation.scopes.map((scope) => (\n <span\n key={scope}\n className=\"rounded-md bg-accent-purple-subtle px-2 py-0.5 text-xs text-accent-purple\"\n >\n {scope}\n </span>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Right: Revoke button */}\n {delegation.status === \"ACTIVE\" && (\n <button\n onClick={() => onRevoke(delegation)}\n disabled={isRevoking || !canRevoke}\n aria-disabled={!canRevoke}\n className=\"inline-flex shrink-0 items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isRevoking ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n {tr(\"devportal.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n );\n};\n\n/**\n * Revocation history section (dimmed, for revoked/expired delegations)\n */\nconst RevocationHistory: FC<{ delegations: AppUserDelegation[] }> = ({\n delegations,\n}) => {\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\n if (delegations.length === 0) return null;\n\n return (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">\n {tr(\"devportal.revocationHistory\", \"Revocation History\")}\n </h2>\n <div className=\"space-y-3 opacity-60\">\n {delegations.map((delegation) => (\n <div\n key={delegation.id}\n className=\"flex items-center justify-between rounded-lg border bg-muted/30 p-4\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground\">\n <AppTypeIcon\n type={delegation.app?.type ?? \"SERVICE\"}\n className=\"h-5 w-5\"\n />\n </div>\n <div>\n <h3 className=\"font-medium\">\n {delegation.app?.name ??\n tr(\"devportal.unknownApp\", \"Unknown App\")}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n {delegation.status === \"REVOKED\"\n ? tr(\"devportal.revoked\", \"Revoked\")\n : tr(\"devportal.expired\", \"Expired\")}{\" \"}\n {delegation.revokedAt\n ? formatRelativeTime(delegation.revokedAt)\n : \"\"}\n </p>\n </div>\n </div>\n <DelegationStatusBadge status={delegation.status} />\n </div>\n ))}\n </div>\n </div>\n );\n};\n\n// =============================================================================\n// MAIN COMPONENT\n// =============================================================================\n\n/**\n * Delegations management page for the Developer Portal.\n *\n * Consolidates delegation management previously at /auth/delegations in\n * microfe-auth. Shows two tabs:\n * - \"Apps Acting as Me\": delegations where the current user is the subject\n * - \"Delegations I Granted\": delegations the current user granted to others\n */\nexport const DelegationsPage: 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 { allowed: canRevoke } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.DELEGATION_REVOKE,\n );\n // ---------------------------------------------------------------------------\n // State\n // ---------------------------------------------------------------------------\n const [activeTab, setActiveTab] = useState<ActiveTab>(\"acting-as-me\");\n const [revokeTargets, setRevokeTargets] = useState<AppUserDelegation[]>([]);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [selectedDelegationIds, setSelectedDelegationIds] = useState<\n Set<string>\n >(new Set());\n\n // ---------------------------------------------------------------------------\n // Queries\n // ---------------------------------------------------------------------------\n const {\n data: delegationsData,\n loading: delegationsLoading,\n error: delegationsError,\n refetch: refetchDelegations,\n } = useCurrentUserDelegationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const {\n data: grantedData,\n loading: grantedLoading,\n error: grantedError,\n refetch: refetchGranted,\n } = useCurrentUserGrantedDelegationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n // ---------------------------------------------------------------------------\n // Mutation\n // ---------------------------------------------------------------------------\n const [revokeDelegationMutation, { loading: revokeLoading }] =\n useRevokeDelegationMutation();\n\n // ---------------------------------------------------------------------------\n // Derived data\n // ---------------------------------------------------------------------------\n const delegations: AppUserDelegation[] = (\n delegationsData?.currentUserDelegations ?? []\n ).map(mapDelegation);\n\n const grantedDelegations: AppUserDelegation[] = (\n grantedData?.currentUserGrantedDelegations ?? []\n ).map(mapDelegation);\n\n const activeDelegations = delegations.filter((d) => d.status === \"ACTIVE\");\n const inactiveDelegations = delegations.filter((d) => d.status !== \"ACTIVE\");\n const activeGrantedDelegations = grantedDelegations.filter(\n (d) => d.status === \"ACTIVE\",\n );\n const visibleActiveDelegations =\n activeTab === \"acting-as-me\" ? activeDelegations : activeGrantedDelegations;\n const selectedActiveDelegations = useMemo(\n () =>\n visibleActiveDelegations.filter((delegation) =>\n selectedDelegationIds.has(delegation.id),\n ),\n [selectedDelegationIds, visibleActiveDelegations],\n );\n const allVisibleSelected =\n visibleActiveDelegations.length > 0 &&\n visibleActiveDelegations.every((delegation) =>\n selectedDelegationIds.has(delegation.id),\n );\n const partiallySelected =\n visibleActiveDelegations.some((delegation) =>\n selectedDelegationIds.has(delegation.id),\n ) && !allVisibleSelected;\n\n const isLoading =\n activeTab === \"acting-as-me\" ? delegationsLoading : grantedLoading;\n const queryError =\n activeTab === \"acting-as-me\" ? delegationsError : grantedError;\n\n // ---------------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------------\n const handleRevokeClick = useCallback((delegation: AppUserDelegation) => {\n setRevokeTargets([delegation]);\n }, []);\n\n const toggleDelegationSelection = useCallback(\n (delegationId: string, checked: boolean) => {\n setSelectedDelegationIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(delegationId);\n } else {\n next.delete(delegationId);\n }\n return next;\n });\n },\n [],\n );\n\n const toggleAllVisibleDelegations = useCallback(\n (checked: boolean) => {\n setSelectedDelegationIds((prev) => {\n const next = new Set(prev);\n visibleActiveDelegations.forEach((delegation) => {\n if (checked) {\n next.add(delegation.id);\n } else {\n next.delete(delegation.id);\n }\n });\n return next;\n });\n },\n [visibleActiveDelegations],\n );\n\n const clearSelection = useCallback(() => {\n setSelectedDelegationIds(new Set());\n }, []);\n\n const handleBulkRevokeClick = useCallback(() => {\n if (selectedActiveDelegations.length === 0) return;\n setRevokeTargets(selectedActiveDelegations);\n }, [selectedActiveDelegations]);\n\n const handleRevokeConfirm = useCallback(async () => {\n if (revokeTargets.length === 0) return;\n try {\n const results = await Promise.allSettled(\n revokeTargets.map((delegation) =>\n revokeDelegationMutation({\n variables: {\n id: delegation.id,\n reason: tr(\n \"devportal.userRevokedAccessReason\",\n \"User revoked access via Developer Portal\",\n ),\n },\n }),\n ),\n );\n const succeeded = results.filter(\n (result: PromiseSettledResult<unknown>) =>\n result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setSuccessMessage(\n `Revoked ${succeeded} delegation${succeeded === 1 ? \"\" : \"s\"}${failed > 0 ? ` • ${failed} failed` : \"\"}`,\n );\n setSelectedDelegationIds((prev) => {\n const next = new Set(prev);\n revokeTargets.forEach((delegation) => next.delete(delegation.id));\n return next;\n });\n setRevokeTargets([]);\n // Refetch both queries to keep data fresh\n void refetchDelegations();\n void refetchGranted();\n } catch {\n // Error is available via the mutation hook's error state;\n // the ConfirmDialog will close and we show the error via queryError fallback\n setRevokeTargets([]);\n }\n }, [\n revokeTargets,\n revokeDelegationMutation,\n refetchDelegations,\n refetchGranted,\n ]);\n\n const handleDismissSuccess = useCallback(() => {\n setSuccessMessage(null);\n }, []);\n\n // ---------------------------------------------------------------------------\n // Summary stats for header\n // ---------------------------------------------------------------------------\n const totalActive = activeDelegations.length;\n const totalGranted = activeGrantedDelegations.length;\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.INSTALLATION_READ}>\n <PageLayout\n title={tr(\"devportal.appDelegationsTitle\", \"App Delegations\")}\n description={tr(\n \"devportal.appDelegationsDescription\",\n \"Manage which apps can act on your behalf. Revoke access at any time.\",\n )}\n >\n {/* Success message */}\n {successMessage && (\n <SuccessToast\n message={successMessage}\n onDismiss={handleDismissSuccess}\n />\n )}\n\n {/* Error message */}\n {queryError && (\n <ErrorBanner\n message={\n queryError.message ||\n tr(\n \"devportal.failedToLoadDelegations\",\n \"Failed to load delegations\",\n )\n }\n onRetry={() => {\n void refetchDelegations();\n void refetchGranted();\n }}\n />\n )}\n\n {/* Summary stats */}\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Shield className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.activeDelegations\", \"Active Delegations\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{totalActive}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Users className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.grantedByMe\", \"Granted by Me\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{totalGranted}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <AlertTriangle className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.revokedExpired\", \"Revoked / Expired\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">\n {inactiveDelegations.length}\n </p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Clock className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.totalAllTime\", \"Total All-Time\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{delegations.length}</p>\n </div>\n </div>\n\n {/* Tabs */}\n <div className=\"border-b\">\n <div\n className=\"flex overflow-x-auto\"\n role=\"tablist\"\n aria-label={tr(\n \"devportal.delegationSections\",\n \"Delegation sections\",\n )}\n >\n <button\n id=\"delegations-tab-acting-as-me\"\n role=\"tab\"\n aria-selected={activeTab === \"acting-as-me\"}\n onClick={() => {\n clearSelection();\n setActiveTab(\"acting-as-me\");\n }}\n className={cn(\n \"-mb-px border-b-2 px-4 py-3 text-sm font-medium transition-colors\",\n activeTab === \"acting-as-me\"\n ? \"border-primary text-primary\"\n : \"border-transparent text-muted-foreground hover:text-foreground\",\n )}\n >\n {tr(\"devportal.appsActingAsMe\", \"Apps Acting as Me\")}\n {totalActive > 0 && (\n <span className=\"ml-2 inline-flex items-center justify-center rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {totalActive}\n </span>\n )}\n </button>\n <button\n id=\"delegations-tab-granted-by-me\"\n role=\"tab\"\n aria-selected={activeTab === \"granted-by-me\"}\n onClick={() => {\n clearSelection();\n setActiveTab(\"granted-by-me\");\n }}\n className={cn(\n \"-mb-px border-b-2 px-4 py-3 text-sm font-medium transition-colors\",\n activeTab === \"granted-by-me\"\n ? \"border-primary text-primary\"\n : \"border-transparent text-muted-foreground hover:text-foreground\",\n )}\n >\n {tr(\"devportal.delegationsIGranted\", \"Delegations I Granted\")}\n {totalGranted > 0 && (\n <span className=\"ml-2 inline-flex items-center justify-center rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {totalGranted}\n </span>\n )}\n </button>\n </div>\n </div>\n\n {visibleActiveDelegations.length > 0 && (\n <div className=\"rounded-lg border bg-card p-4\">\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={toggleAllVisibleDelegations}\n ariaLabel={tr(\n \"devportal.selectAllVisibleDelegations\",\n \"Select all visible delegations\",\n )}\n />\n <span className=\"text-sm font-medium\">\n {tr(\"devportal.selectAllVisible\", \"Select all visible\")}\n </span>\n </div>\n {selectedDelegationIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\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-muted px-3 py-1 text-sm font-medium text-muted-foreground\">\n {selectedDelegationIds.size}{\" \"}\n {tr(\"devportal.selected\", \"selected\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{totalActive}</p>\n </div>\n </div>\n )}\n\n {/* Loading */}\n {isLoading && !delegationsData && !grantedData && (\n <LoadingState\n message={tr(\n \"devportal.loadingDelegations\",\n \"Loading delegations...\",\n )}\n />\n )}\n\n {/* ------------------------------------------------------------------- */}\n {/* Tab 1: Apps Acting as Me */}\n {/* ------------------------------------------------------------------- */}\n {!isLoading && activeTab === \"acting-as-me\" && (\n <div\n className=\"space-y-8\"\n id=\"delegations-panel-acting-as-me\"\n role=\"tabpanel\"\n aria-labelledby=\"delegations-tab-acting-as-me\"\n tabIndex={0}\n >\n {activeDelegations.length === 0 &&\n inactiveDelegations.length === 0 ? (\n <EmptyState\n icon={<Shield size={32} />}\n title={tr(\n \"devportal.noActiveDelegations\",\n \"No Active Delegations\",\n )}\n description={tr(\n \"devportal.noAppsAuthorizedOnYourBehalf\",\n \"No apps are currently authorized to act on your behalf.\",\n )}\n />\n ) : (\n <>\n {/* Active delegation cards */}\n {activeDelegations.length === 0 ? (\n <EmptyState\n icon={<Shield size={32} />}\n title={tr(\n \"devportal.noActiveDelegations\",\n \"No Active Delegations\",\n )}\n description={tr(\n \"devportal.allPreviousDelegationsRevokedOrExpired\",\n \"All previous delegations have been revoked or expired.\",\n )}\n />\n ) : (\n <div className=\"space-y-4\">\n {activeDelegations.map((delegation) => (\n <DelegationCard\n key={delegation.id}\n delegation={delegation}\n onRevoke={handleRevokeClick}\n isRevoking={\n revokeLoading &&\n revokeTargets.some(\n (item) => item.id === delegation.id,\n )\n }\n selected={selectedDelegationIds.has(delegation.id)}\n onToggleSelect={(checked) =>\n toggleDelegationSelection(delegation.id, checked)\n }\n canRevoke={canRevoke}\n />\n ))}\n </div>\n )}\n\n {/* Revocation history */}\n <RevocationHistory delegations={inactiveDelegations} />\n </>\n )}\n </div>\n )}\n\n {/* ------------------------------------------------------------------- */}\n {/* Tab 2: Delegations I Granted */}\n {/* ------------------------------------------------------------------- */}\n {!isLoading && activeTab === \"granted-by-me\" && (\n <div\n className=\"space-y-4\"\n id=\"delegations-panel-granted-by-me\"\n role=\"tabpanel\"\n aria-labelledby=\"delegations-tab-granted-by-me\"\n tabIndex={0}\n >\n {grantedDelegations.length === 0 ? (\n <EmptyState\n icon={<Users size={32} />}\n title={tr(\n \"devportal.noDelegationsGranted\",\n \"No Delegations Granted\",\n )}\n description={tr(\n \"devportal.noGrantedDelegationsDescription\",\n \"You haven't granted any app delegations to other users.\",\n )}\n />\n ) : (\n grantedDelegations.map((delegation) => (\n <GrantedDelegationCard\n key={delegation.id}\n delegation={delegation}\n onRevoke={handleRevokeClick}\n isRevoking={\n revokeLoading &&\n revokeTargets.some((item) => item.id === delegation.id)\n }\n selected={selectedDelegationIds.has(delegation.id)}\n onToggleSelect={(checked) =>\n toggleDelegationSelection(delegation.id, checked)\n }\n canRevoke={canRevoke}\n />\n ))\n )}\n </div>\n )}\n\n {/* Info card */}\n <div className=\"rounded-lg border border-border-subtle bg-status-info-bg-subtle p-5\">\n <div className=\"flex gap-4\">\n <AlertCircle className=\"h-6 w-6 shrink-0 text-accent-blue\" />\n <div>\n <h3 className=\"font-semibold text-status-info-text\">\n {tr(\"devportal.aboutAppDelegations\", \"About App Delegations\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-info-text\">\n {tr(\n \"devportal.aboutAppDelegationsText\",\n \"App delegations allow automated services, bots, and integrations to perform actions on your behalf. Each delegation specifies which permissions (scopes) and workspaces the app can access. You can revoke access at any time, which immediately prevents the app from acting as you.\",\n )}\n </p>\n </div>\n </div>\n </div>\n\n {/* Revoke confirmation dialog */}\n <ConfirmDialog\n open={revokeTargets.length > 0}\n onOpenChange={(open) => {\n if (!open) setRevokeTargets([]);\n }}\n onConfirm={handleRevokeConfirm}\n title={\n revokeTargets.length > 1\n ? tr(\"devportal.revokeDelegations\", \"Revoke Delegations\")\n : tr(\"devportal.revokeDelegation\", \"Revoke Delegation\")\n }\n description={\n revokeTargets.length > 1\n ? tr(\n \"devportal.revokeDelegationsBulkConfirm\",\n `Are you sure you want to revoke ${revokeTargets.length} selected delegations? Those apps will no longer be able to act on the delegated users' behalf.`,\n )\n : tr(\n \"devportal.revokeDelegationSingleConfirm\",\n `Are you sure you want to revoke access for \"${revokeTargets[0]?.app?.name ?? \"this app\"}\"? This app will no longer be able to act on your behalf.`,\n )\n }\n confirmLabel={tr(\"devportal.revokeAccess\", \"Revoke Access\")}\n cancelLabel={tr(\"devportal.cancel\", \"Cancel\")}\n variant=\"danger\"\n loading={revokeLoading}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DelegationsPage.js","names":[],"sources":["../../../src/devportal/pages/DelegationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useCallback, useEffect, useMemo } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Shield,\n Users,\n Clock,\n XCircle,\n AlertTriangle,\n Loader2,\n AlertCircle,\n Bot,\n Zap,\n Cog,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { LoadingState } from \"../components/LoadingState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { cn } from \"../utils/cn\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { AppUserDelegation, DelegationStatus } from \"../types\";\nimport {\n useCurrentUserDelegationsQuery,\n useCurrentUserGrantedDelegationsQuery,\n useRevokeDelegationMutation,\n} from \"../../generated/global-operations\";\n\n// =============================================================================\n// HELPERS\n// =============================================================================\n\ntype ActiveTab = \"acting-as-me\" | \"granted-by-me\";\n\n/**\n * Map a GraphQL delegation fragment to the local AppUserDelegation type\n */\nfunction mapDelegation(d: {\n id: string;\n appId: string;\n userId: string;\n scopes: string[];\n workspaceIds?: string[] | null;\n status: string;\n grantedAt: string;\n grantedBy: string;\n expiresAt?: string | null;\n revokedAt?: string | null;\n revokedBy?: string | null;\n app: {\n id: string;\n name: string;\n description?: string | null;\n type: string;\n status: string;\n };\n}): AppUserDelegation {\n return {\n id: d.id,\n appId: d.appId,\n userId: d.userId,\n scopes: d.scopes,\n workspaceIds: d.workspaceIds ?? [],\n status: d.status as DelegationStatus,\n grantedAt: d.grantedAt,\n grantedBy: d.grantedBy,\n expiresAt: d.expiresAt ?? undefined,\n revokedAt: d.revokedAt ?? undefined,\n revokedBy: d.revokedBy ?? undefined,\n app: {\n id: d.app.id,\n name: d.app.name,\n description: d.app.description ?? undefined,\n type: d.app.type,\n status: d.app.status,\n },\n };\n}\n\n/**\n * Format a date string to relative time (\"2 hours ago\", \"3 days ago\", etc.)\n */\nfunction formatRelativeTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffWeeks = Math.floor(diffDays / 7);\n const diffMonths = Math.floor(diffDays / 30);\n\n if (diffSeconds < 60) return \"just now\";\n if (diffMinutes < 60)\n return `${diffMinutes} minute${diffMinutes !== 1 ? \"s\" : \"\"} ago`;\n if (diffHours < 24)\n return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? \"s\" : \"\"} ago`;\n if (diffWeeks < 5)\n return `${diffWeeks} week${diffWeeks !== 1 ? \"s\" : \"\"} ago`;\n if (diffMonths < 12)\n return `${diffMonths} month${diffMonths !== 1 ? \"s\" : \"\"} ago`;\n\n return date.toLocaleDateString();\n}\n\n/**\n * Format a future date as relative time (\"in 3 days\", \"in 2 hours\", etc.)\n */\nfunction formatExpirationTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n\n if (diffMs <= 0) return \"expired\";\n\n const diffMinutes = Math.floor(diffMs / (1000 * 60));\n const diffHours = Math.floor(diffMs / (1000 * 60 * 60));\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n\n if (diffMinutes < 60)\n return `in ${diffMinutes} minute${diffMinutes !== 1 ? \"s\" : \"\"}`;\n if (diffHours < 24)\n return `in ${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"}`;\n return `in ${diffDays} day${diffDays !== 1 ? \"s\" : \"\"}`;\n}\n\n// =============================================================================\n// SUB-COMPONENTS\n// =============================================================================\n\n/**\n * Icon for app type (SERVICE, BOT, INTEGRATION, etc.)\n */\nconst AppTypeIcon: FC<{ type: string; className?: string }> = ({\n type,\n className,\n}) => {\n const iconProps = { className: cn(\"h-5 w-5\", className) };\n\n switch (type) {\n case \"SERVICE\":\n return <Cog {...iconProps} />;\n case \"BOT\":\n return <Bot {...iconProps} />;\n case \"INTEGRATION\":\n return <Zap {...iconProps} />;\n default:\n return <Shield {...iconProps} />;\n }\n};\n\n/**\n * Delegation status badge with color coding\n */\nconst DelegationStatusBadge: FC<{ status: DelegationStatus }> = ({\n status,\n}) => {\n const styles: Record<DelegationStatus, string> = {\n ACTIVE: \"bg-status-success-bg-subtle text-status-success-text\",\n REVOKED: \"bg-status-error-bg-subtle text-status-error-text\",\n EXPIRED: \"bg-bg-sunken text-text-primary\",\n };\n\n return (\n <span\n className={cn(\n \"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium\",\n styles[status],\n )}\n >\n {status}\n </span>\n );\n};\n\n/**\n * Success toast that auto-dismisses\n */\nconst SuccessToast: FC<{ message: string; onDismiss: () => void }> = ({\n message,\n onDismiss,\n}) => {\n useEffect(() => {\n const timer = setTimeout(onDismiss, 5000);\n return () => clearTimeout(timer);\n }, [onDismiss]);\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-status-success-bg-subtle p-4\">\n <div className=\"flex items-center gap-3\">\n <Shield className=\"h-5 w-5 text-status-success-text\" />\n <p className=\"text-sm font-medium text-status-success-text\">\n {message}\n </p>\n </div>\n </div>\n );\n};\n\n/**\n * Error banner\n */\nconst ErrorBanner: FC<{ message: string; onRetry?: () => void }> = ({\n message,\n onRetry,\n}) => {\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 return (\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-4\">\n <div className=\"flex items-center gap-3\">\n <AlertCircle className=\"h-5 w-5 text-destructive\" />\n <p className=\"flex-1 text-sm text-destructive\">{message}</p>\n {onRetry && (\n <button\n onClick={onRetry}\n className=\"rounded-md border px-3 py-1.5 text-xs font-medium hover:bg-muted transition-colors\"\n >\n {tr(\"devportal.retry\", \"Retry\")}\n </button>\n )}\n </div>\n </div>\n );\n};\n\n/**\n * Single delegation card for \"Apps Acting as Me\" tab\n */\nconst DelegationCard: FC<{\n delegation: AppUserDelegation;\n onRevoke: (delegation: AppUserDelegation) => void;\n isRevoking: boolean;\n selected: boolean;\n onToggleSelect: (checked: boolean) => void;\n canRevoke: boolean;\n}> = ({\n delegation,\n onRevoke,\n isRevoking,\n selected,\n onToggleSelect,\n canRevoke,\n}) => {\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 return (\n <div\n className={cn(\n \"rounded-lg border bg-card p-5 transition-colors hover:border-border/80\",\n selected && \"border-primary bg-primary/5\",\n delegation.status !== \"ACTIVE\" && \"opacity-60\",\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Left: App info */}\n <div className=\"flex items-start gap-4 min-w-0\">\n {delegation.status === \"ACTIVE\" && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={tr(\n \"devportal.selectDelegationAriaLabel\",\n `Select delegation ${delegation.app?.name ?? delegation.id}`,\n )}\n className=\"mt-1\"\n />\n </div>\n )}\n <div\n className={cn(\n \"flex h-12 w-12 shrink-0 items-center justify-center rounded-lg\",\n delegation.status === \"ACTIVE\"\n ? \"bg-primary/10 text-primary\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n <AppTypeIcon\n type={delegation.app?.type ?? \"SERVICE\"}\n className=\"h-6 w-6\"\n />\n </div>\n\n <div className=\"min-w-0 space-y-2\">\n {/* Name + status */}\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate font-semibold\">\n {delegation.app?.name ??\n tr(\"devportal.unknownApp\", \"Unknown App\")}\n </h3>\n <DelegationStatusBadge status={delegation.status} />\n </div>\n\n {/* Description */}\n {delegation.app?.description && (\n <p className=\"text-sm text-muted-foreground line-clamp-2\">\n {delegation.app.description}\n </p>\n )}\n\n {/* Metadata row */}\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.granted\", \"Granted\")}{\" \"}\n {formatRelativeTime(delegation.grantedAt)}\n </span>\n {delegation.expiresAt && (\n <span className=\"flex items-center gap-1\">\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.expires\", \"Expires\")}{\" \"}\n {formatExpirationTime(delegation.expiresAt)}\n </span>\n )}\n <span\n className={cn(\n \"rounded-md px-2 py-0.5 text-xs font-mono\",\n \"bg-muted text-muted-foreground\",\n )}\n >\n {delegation.app?.type ?? \"UNKNOWN\"}\n </span>\n </div>\n\n {/* Scopes */}\n {delegation.scopes.length > 0 && (\n <div>\n <p className=\"mb-1.5 text-xs text-muted-foreground\">\n {tr(\"devportal.permissions\", \"Permissions:\")}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {delegation.scopes.map((scope) => (\n <span\n key={scope}\n className=\"rounded-md bg-status-info-bg-subtle px-2 py-0.5 text-xs text-status-info-text\"\n >\n {scope}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Workspace count */}\n {delegation.workspaceIds.length > 0 && (\n <p className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n <Users className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.accessToWorkspaces\", \"Access to\")}{\" \"}\n {delegation.workspaceIds.length}{\" \"}\n {tr(\"devportal.workspace\", \"workspace\")}\n {delegation.workspaceIds.length !== 1\n ? tr(\"devportal.workspacePluralSuffix\", \"s\")\n : \"\"}\n </p>\n )}\n </div>\n </div>\n\n {/* Right: Revoke button (only for active delegations) */}\n {delegation.status === \"ACTIVE\" && (\n <button\n onClick={() => onRevoke(delegation)}\n disabled={isRevoking || !canRevoke}\n aria-disabled={!canRevoke}\n className=\"inline-flex shrink-0 items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isRevoking ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n {tr(\"devportal.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n );\n};\n\n/**\n * Single delegation card for \"Delegations I Granted\" tab\n */\nconst GrantedDelegationCard: FC<{\n delegation: AppUserDelegation;\n onRevoke: (delegation: AppUserDelegation) => void;\n isRevoking: boolean;\n selected: boolean;\n onToggleSelect: (checked: boolean) => void;\n canRevoke: boolean;\n}> = ({\n delegation,\n onRevoke,\n isRevoking,\n selected,\n onToggleSelect,\n canRevoke,\n}) => {\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 return (\n <div\n className={cn(\n \"rounded-lg border bg-card p-5 transition-colors hover:border-border/80\",\n selected && \"border-primary bg-primary/5\",\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n {/* Left: App info */}\n <div className=\"flex items-start gap-4 min-w-0\">\n {delegation.status === \"ACTIVE\" && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={tr(\n \"devportal.selectDelegationAriaLabel\",\n `Select delegation ${delegation.app?.name ?? delegation.id}`,\n )}\n className=\"mt-1\"\n />\n </div>\n )}\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-accent-purple-subtle text-accent-purple\">\n <AppTypeIcon\n type={delegation.app?.type ?? \"SERVICE\"}\n className=\"h-6 w-6\"\n />\n </div>\n\n <div className=\"min-w-0 space-y-2\">\n {/* Name + status */}\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate font-semibold\">\n {delegation.app?.name ??\n tr(\"devportal.unknownApp\", \"Unknown App\")}\n </h3>\n <DelegationStatusBadge status={delegation.status} />\n </div>\n\n {/* Target user */}\n <p className=\"text-sm text-muted-foreground\">\n {tr(\"devportal.grantedToUser\", \"Granted to user:\")}{\" \"}\n <code className=\"rounded bg-muted px-1.5 py-0.5 text-xs\">\n {delegation.userId}\n </code>\n </p>\n\n {/* Time */}\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.granted\", \"Granted\")}{\" \"}\n {formatRelativeTime(delegation.grantedAt)}\n </span>\n {delegation.expiresAt && (\n <span className=\"flex items-center gap-1\">\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {tr(\"devportal.expires\", \"Expires\")}{\" \"}\n {new Date(delegation.expiresAt).toLocaleDateString()}\n </span>\n )}\n </div>\n\n {/* Scopes */}\n {delegation.scopes.length > 0 && (\n <div>\n <p className=\"mb-1.5 text-xs text-muted-foreground\">\n {tr(\"devportal.permissionsGranted\", \"Permissions granted:\")}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {delegation.scopes.map((scope) => (\n <span\n key={scope}\n className=\"rounded-md bg-accent-purple-subtle px-2 py-0.5 text-xs text-accent-purple\"\n >\n {scope}\n </span>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Right: Revoke button */}\n {delegation.status === \"ACTIVE\" && (\n <button\n onClick={() => onRevoke(delegation)}\n disabled={isRevoking || !canRevoke}\n aria-disabled={!canRevoke}\n className=\"inline-flex shrink-0 items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isRevoking ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n {tr(\"devportal.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n );\n};\n\n/**\n * Revocation history section (dimmed, for revoked/expired delegations)\n */\nconst RevocationHistory: FC<{ delegations: AppUserDelegation[] }> = ({\n delegations,\n}) => {\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\n if (delegations.length === 0) return null;\n\n return (\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">\n {tr(\"devportal.revocationHistory\", \"Revocation History\")}\n </h2>\n <div className=\"space-y-3 opacity-60\">\n {delegations.map((delegation) => (\n <div\n key={delegation.id}\n className=\"flex items-center justify-between rounded-lg border bg-muted/30 p-4\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground\">\n <AppTypeIcon\n type={delegation.app?.type ?? \"SERVICE\"}\n className=\"h-5 w-5\"\n />\n </div>\n <div>\n <h3 className=\"font-medium\">\n {delegation.app?.name ??\n tr(\"devportal.unknownApp\", \"Unknown App\")}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n {delegation.status === \"REVOKED\"\n ? tr(\"devportal.revoked\", \"Revoked\")\n : tr(\"devportal.expired\", \"Expired\")}{\" \"}\n {delegation.revokedAt\n ? formatRelativeTime(delegation.revokedAt)\n : \"\"}\n </p>\n </div>\n </div>\n <DelegationStatusBadge status={delegation.status} />\n </div>\n ))}\n </div>\n </div>\n );\n};\n\n// =============================================================================\n// MAIN COMPONENT\n// =============================================================================\n\n/**\n * Delegations management page for the Developer Portal.\n *\n * Consolidates delegation management previously at /auth/delegations in\n * microfe-auth. Shows two tabs:\n * - \"Apps Acting as Me\": delegations where the current user is the subject\n * - \"Delegations I Granted\": delegations the current user granted to others\n */\nexport const DelegationsPage: 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 { allowed: canRevoke } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.DELEGATION_REVOKE,\n );\n // ---------------------------------------------------------------------------\n // State\n // ---------------------------------------------------------------------------\n const [activeTab, setActiveTab] = useState<ActiveTab>(\"acting-as-me\");\n const [revokeTargets, setRevokeTargets] = useState<AppUserDelegation[]>([]);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [selectedDelegationIds, setSelectedDelegationIds] = useState<\n Set<string>\n >(new Set());\n\n // ---------------------------------------------------------------------------\n // Queries\n // ---------------------------------------------------------------------------\n const {\n data: delegationsData,\n loading: delegationsLoading,\n error: delegationsError,\n refetch: refetchDelegations,\n } = useCurrentUserDelegationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const {\n data: grantedData,\n loading: grantedLoading,\n error: grantedError,\n refetch: refetchGranted,\n } = useCurrentUserGrantedDelegationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n // ---------------------------------------------------------------------------\n // Mutation\n // ---------------------------------------------------------------------------\n const [revokeDelegationMutation, { loading: revokeLoading }] =\n useRevokeDelegationMutation();\n\n // ---------------------------------------------------------------------------\n // Derived data\n // ---------------------------------------------------------------------------\n const delegations: AppUserDelegation[] = (\n delegationsData?.currentUserDelegations?.delegations ?? []\n ).map(mapDelegation);\n\n const grantedDelegations: AppUserDelegation[] = (\n grantedData?.currentUserGrantedDelegations?.delegations ?? []\n ).map(mapDelegation);\n\n const activeDelegations = delegations.filter((d) => d.status === \"ACTIVE\");\n const inactiveDelegations = delegations.filter((d) => d.status !== \"ACTIVE\");\n const activeGrantedDelegations = grantedDelegations.filter(\n (d) => d.status === \"ACTIVE\",\n );\n const visibleActiveDelegations =\n activeTab === \"acting-as-me\" ? activeDelegations : activeGrantedDelegations;\n const selectedActiveDelegations = useMemo(\n () =>\n visibleActiveDelegations.filter((delegation) =>\n selectedDelegationIds.has(delegation.id),\n ),\n [selectedDelegationIds, visibleActiveDelegations],\n );\n const allVisibleSelected =\n visibleActiveDelegations.length > 0 &&\n visibleActiveDelegations.every((delegation) =>\n selectedDelegationIds.has(delegation.id),\n );\n const partiallySelected =\n visibleActiveDelegations.some((delegation) =>\n selectedDelegationIds.has(delegation.id),\n ) && !allVisibleSelected;\n\n const isLoading =\n activeTab === \"acting-as-me\" ? delegationsLoading : grantedLoading;\n const queryError =\n activeTab === \"acting-as-me\" ? delegationsError : grantedError;\n\n // ---------------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------------\n const handleRevokeClick = useCallback((delegation: AppUserDelegation) => {\n setRevokeTargets([delegation]);\n }, []);\n\n const toggleDelegationSelection = useCallback(\n (delegationId: string, checked: boolean) => {\n setSelectedDelegationIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(delegationId);\n } else {\n next.delete(delegationId);\n }\n return next;\n });\n },\n [],\n );\n\n const toggleAllVisibleDelegations = useCallback(\n (checked: boolean) => {\n setSelectedDelegationIds((prev) => {\n const next = new Set(prev);\n visibleActiveDelegations.forEach((delegation) => {\n if (checked) {\n next.add(delegation.id);\n } else {\n next.delete(delegation.id);\n }\n });\n return next;\n });\n },\n [visibleActiveDelegations],\n );\n\n const clearSelection = useCallback(() => {\n setSelectedDelegationIds(new Set());\n }, []);\n\n const handleBulkRevokeClick = useCallback(() => {\n if (selectedActiveDelegations.length === 0) return;\n setRevokeTargets(selectedActiveDelegations);\n }, [selectedActiveDelegations]);\n\n const handleRevokeConfirm = useCallback(async () => {\n if (revokeTargets.length === 0) return;\n try {\n const results = await Promise.allSettled(\n revokeTargets.map((delegation) =>\n revokeDelegationMutation({\n variables: {\n id: delegation.id,\n reason: tr(\n \"devportal.userRevokedAccessReason\",\n \"User revoked access via Developer Portal\",\n ),\n },\n }),\n ),\n );\n const succeeded = results.filter(\n (result: PromiseSettledResult<unknown>) =>\n result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setSuccessMessage(\n `Revoked ${succeeded} delegation${succeeded === 1 ? \"\" : \"s\"}${failed > 0 ? ` • ${failed} failed` : \"\"}`,\n );\n setSelectedDelegationIds((prev) => {\n const next = new Set(prev);\n revokeTargets.forEach((delegation) => next.delete(delegation.id));\n return next;\n });\n setRevokeTargets([]);\n // Refetch both queries to keep data fresh\n void refetchDelegations();\n void refetchGranted();\n } catch {\n // Error is available via the mutation hook's error state;\n // the ConfirmDialog will close and we show the error via queryError fallback\n setRevokeTargets([]);\n }\n }, [\n revokeTargets,\n revokeDelegationMutation,\n refetchDelegations,\n refetchGranted,\n ]);\n\n const handleDismissSuccess = useCallback(() => {\n setSuccessMessage(null);\n }, []);\n\n // ---------------------------------------------------------------------------\n // Summary stats for header\n // ---------------------------------------------------------------------------\n const totalActive = activeDelegations.length;\n const totalGranted = activeGrantedDelegations.length;\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.INSTALLATION_READ}>\n <PageLayout\n title={tr(\"devportal.appDelegationsTitle\", \"App Delegations\")}\n description={tr(\n \"devportal.appDelegationsDescription\",\n \"Manage which apps can act on your behalf. Revoke access at any time.\",\n )}\n >\n {/* Success message */}\n {successMessage && (\n <SuccessToast\n message={successMessage}\n onDismiss={handleDismissSuccess}\n />\n )}\n\n {/* Error message */}\n {queryError && (\n <ErrorBanner\n message={\n queryError.message ||\n tr(\n \"devportal.failedToLoadDelegations\",\n \"Failed to load delegations\",\n )\n }\n onRetry={() => {\n void refetchDelegations();\n void refetchGranted();\n }}\n />\n )}\n\n {/* Summary stats */}\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Shield className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.activeDelegations\", \"Active Delegations\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{totalActive}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Users className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.grantedByMe\", \"Granted by Me\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{totalGranted}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <AlertTriangle className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.revokedExpired\", \"Revoked / Expired\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">\n {inactiveDelegations.length}\n </p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Clock className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n {tr(\"devportal.totalAllTime\", \"Total All-Time\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{delegations.length}</p>\n </div>\n </div>\n\n {/* Tabs */}\n <div className=\"border-b\">\n <div\n className=\"flex overflow-x-auto\"\n role=\"tablist\"\n aria-label={tr(\n \"devportal.delegationSections\",\n \"Delegation sections\",\n )}\n >\n <button\n id=\"delegations-tab-acting-as-me\"\n role=\"tab\"\n aria-selected={activeTab === \"acting-as-me\"}\n onClick={() => {\n clearSelection();\n setActiveTab(\"acting-as-me\");\n }}\n className={cn(\n \"-mb-px border-b-2 px-4 py-3 text-sm font-medium transition-colors\",\n activeTab === \"acting-as-me\"\n ? \"border-primary text-primary\"\n : \"border-transparent text-muted-foreground hover:text-foreground\",\n )}\n >\n {tr(\"devportal.appsActingAsMe\", \"Apps Acting as Me\")}\n {totalActive > 0 && (\n <span className=\"ml-2 inline-flex items-center justify-center rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {totalActive}\n </span>\n )}\n </button>\n <button\n id=\"delegations-tab-granted-by-me\"\n role=\"tab\"\n aria-selected={activeTab === \"granted-by-me\"}\n onClick={() => {\n clearSelection();\n setActiveTab(\"granted-by-me\");\n }}\n className={cn(\n \"-mb-px border-b-2 px-4 py-3 text-sm font-medium transition-colors\",\n activeTab === \"granted-by-me\"\n ? \"border-primary text-primary\"\n : \"border-transparent text-muted-foreground hover:text-foreground\",\n )}\n >\n {tr(\"devportal.delegationsIGranted\", \"Delegations I Granted\")}\n {totalGranted > 0 && (\n <span className=\"ml-2 inline-flex items-center justify-center rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary\">\n {totalGranted}\n </span>\n )}\n </button>\n </div>\n </div>\n\n {visibleActiveDelegations.length > 0 && (\n <div className=\"rounded-lg border bg-card p-4\">\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={toggleAllVisibleDelegations}\n ariaLabel={tr(\n \"devportal.selectAllVisibleDelegations\",\n \"Select all visible delegations\",\n )}\n />\n <span className=\"text-sm font-medium\">\n {tr(\"devportal.selectAllVisible\", \"Select all visible\")}\n </span>\n </div>\n {selectedDelegationIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\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-muted px-3 py-1 text-sm font-medium text-muted-foreground\">\n {selectedDelegationIds.size}{\" \"}\n {tr(\"devportal.selected\", \"selected\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{totalActive}</p>\n </div>\n </div>\n )}\n\n {/* Loading */}\n {isLoading && !delegationsData && !grantedData && (\n <LoadingState\n message={tr(\n \"devportal.loadingDelegations\",\n \"Loading delegations...\",\n )}\n />\n )}\n\n {/* ------------------------------------------------------------------- */}\n {/* Tab 1: Apps Acting as Me */}\n {/* ------------------------------------------------------------------- */}\n {!isLoading && activeTab === \"acting-as-me\" && (\n <div\n className=\"space-y-8\"\n id=\"delegations-panel-acting-as-me\"\n role=\"tabpanel\"\n aria-labelledby=\"delegations-tab-acting-as-me\"\n tabIndex={0}\n >\n {activeDelegations.length === 0 &&\n inactiveDelegations.length === 0 ? (\n <EmptyState\n icon={<Shield size={32} />}\n title={tr(\n \"devportal.noActiveDelegations\",\n \"No Active Delegations\",\n )}\n description={tr(\n \"devportal.noAppsAuthorizedOnYourBehalf\",\n \"No apps are currently authorized to act on your behalf.\",\n )}\n />\n ) : (\n <>\n {/* Active delegation cards */}\n {activeDelegations.length === 0 ? (\n <EmptyState\n icon={<Shield size={32} />}\n title={tr(\n \"devportal.noActiveDelegations\",\n \"No Active Delegations\",\n )}\n description={tr(\n \"devportal.allPreviousDelegationsRevokedOrExpired\",\n \"All previous delegations have been revoked or expired.\",\n )}\n />\n ) : (\n <div className=\"space-y-4\">\n {activeDelegations.map((delegation) => (\n <DelegationCard\n key={delegation.id}\n delegation={delegation}\n onRevoke={handleRevokeClick}\n isRevoking={\n revokeLoading &&\n revokeTargets.some(\n (item) => item.id === delegation.id,\n )\n }\n selected={selectedDelegationIds.has(delegation.id)}\n onToggleSelect={(checked) =>\n toggleDelegationSelection(delegation.id, checked)\n }\n canRevoke={canRevoke}\n />\n ))}\n </div>\n )}\n\n {/* Revocation history */}\n <RevocationHistory delegations={inactiveDelegations} />\n </>\n )}\n </div>\n )}\n\n {/* ------------------------------------------------------------------- */}\n {/* Tab 2: Delegations I Granted */}\n {/* ------------------------------------------------------------------- */}\n {!isLoading && activeTab === \"granted-by-me\" && (\n <div\n className=\"space-y-4\"\n id=\"delegations-panel-granted-by-me\"\n role=\"tabpanel\"\n aria-labelledby=\"delegations-tab-granted-by-me\"\n tabIndex={0}\n >\n {grantedDelegations.length === 0 ? (\n <EmptyState\n icon={<Users size={32} />}\n title={tr(\n \"devportal.noDelegationsGranted\",\n \"No Delegations Granted\",\n )}\n description={tr(\n \"devportal.noGrantedDelegationsDescription\",\n \"You haven't granted any app delegations to other users.\",\n )}\n />\n ) : (\n grantedDelegations.map((delegation) => (\n <GrantedDelegationCard\n key={delegation.id}\n delegation={delegation}\n onRevoke={handleRevokeClick}\n isRevoking={\n revokeLoading &&\n revokeTargets.some((item) => item.id === delegation.id)\n }\n selected={selectedDelegationIds.has(delegation.id)}\n onToggleSelect={(checked) =>\n toggleDelegationSelection(delegation.id, checked)\n }\n canRevoke={canRevoke}\n />\n ))\n )}\n </div>\n )}\n\n {/* Info card */}\n <div className=\"rounded-lg border border-border-subtle bg-status-info-bg-subtle p-5\">\n <div className=\"flex gap-4\">\n <AlertCircle className=\"h-6 w-6 shrink-0 text-accent-blue\" />\n <div>\n <h3 className=\"font-semibold text-status-info-text\">\n {tr(\"devportal.aboutAppDelegations\", \"About App Delegations\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-info-text\">\n {tr(\n \"devportal.aboutAppDelegationsText\",\n \"App delegations allow automated services, bots, and integrations to perform actions on your behalf. Each delegation specifies which permissions (scopes) and workspaces the app can access. You can revoke access at any time, which immediately prevents the app from acting as you.\",\n )}\n </p>\n </div>\n </div>\n </div>\n\n {/* Revoke confirmation dialog */}\n <ConfirmDialog\n open={revokeTargets.length > 0}\n onOpenChange={(open) => {\n if (!open) setRevokeTargets([]);\n }}\n onConfirm={handleRevokeConfirm}\n title={\n revokeTargets.length > 1\n ? tr(\"devportal.revokeDelegations\", \"Revoke Delegations\")\n : tr(\"devportal.revokeDelegation\", \"Revoke Delegation\")\n }\n description={\n revokeTargets.length > 1\n ? tr(\n \"devportal.revokeDelegationsBulkConfirm\",\n `Are you sure you want to revoke ${revokeTargets.length} selected delegations? Those apps will no longer be able to act on the delegated users' behalf.`,\n )\n : tr(\n \"devportal.revokeDelegationSingleConfirm\",\n `Are you sure you want to revoke access for \"${revokeTargets[0]?.app?.name ?? \"this app\"}\"? This app will no longer be able to act on your behalf.`,\n )\n }\n confirmLabel={tr(\"devportal.revokeAccess\", \"Revoke Access\")}\n cancelLabel={tr(\"devportal.cancel\", \"Cancel\")}\n variant=\"danger\"\n loading={revokeLoading}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -7,14 +7,14 @@ import { StatusBadge as o } from "../components/StatusBadge.js";
|
|
|
7
7
|
import { nativeImpact as s, nativeNotify as c } from "../../utils/nativeBridge.js";
|
|
8
8
|
import { CheckboxField as l, InputField as u, SelectField as d, TagInput as f, TextareaField as p } from "../components/FormField.js";
|
|
9
9
|
import { ConfirmDialog as m } from "../components/ConfirmDialog.js";
|
|
10
|
-
import { useDeactivateAppMutation as
|
|
11
|
-
import { PermissionGuard as
|
|
10
|
+
import { useDeactivateAppMutation as h, useDeleteApplicationMutation as ee, useGetApplicationQuery as te, useSubmitApplicationMutation as ne, useUpdateApplicationMutation as re } from "../../generated/global-operations.js";
|
|
11
|
+
import { PermissionGuard as g } from "../components/PermissionGuard.js";
|
|
12
12
|
import "../components/index.js";
|
|
13
|
-
import { DEVPORTAL_PERMISSIONS as
|
|
14
|
-
import { useDevportalPermission as
|
|
15
|
-
import { useEffect as
|
|
16
|
-
import { useI18n as
|
|
17
|
-
import { AlertCircle as
|
|
13
|
+
import { DEVPORTAL_PERMISSIONS as _ } from "../constants/permissions.js";
|
|
14
|
+
import { useDevportalPermission as v } from "../hooks/usePermission.js";
|
|
15
|
+
import { useEffect as y, useState as b } from "react";
|
|
16
|
+
import { useI18n as x } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
17
|
+
import { AlertCircle as S, AlertTriangle as C, AppWindow as w, Archive as ie, Power as ae, Save as oe, Send as se, Trash2 as ce } from "lucide-react";
|
|
18
18
|
import { useParams as le } from "react-router-dom";
|
|
19
19
|
import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
20
20
|
//#region src/devportal/pages/EditApplicationPage.tsx
|
|
@@ -130,13 +130,20 @@ function pe(e) {
|
|
|
130
130
|
} : null;
|
|
131
131
|
}
|
|
132
132
|
var O = () => {
|
|
133
|
-
let { navigate: a, basePath: O } = e(), { appId: k } = le(), { t: A } =
|
|
133
|
+
let { navigate: a, basePath: O } = e(), { appId: k } = le(), { t: A } = x(), { allowed: j } = v(_.APP_UPDATE), { allowed: M } = v(_.APP_DELETE), N = (e, t) => {
|
|
134
134
|
let n = A(e);
|
|
135
135
|
return n === e ? t : n;
|
|
136
|
-
}, [P, F] =
|
|
136
|
+
}, [P, F] = b(!1), [I, L] = b({}), [R, z] = b(!1), [B, V] = b(!1), [me, H] = b(!1), [U, W] = b(!1), { data: G, loading: he, error: K } = te({
|
|
137
137
|
variables: { id: k },
|
|
138
138
|
skip: !k
|
|
139
|
-
}), [q] =
|
|
139
|
+
}), [q] = re(), [ge] = ee(), [_e] = ne(), [ve] = h(), J = pe(G?.application);
|
|
140
|
+
y(() => {
|
|
141
|
+
let e = J?.name;
|
|
142
|
+
if (!k || !e) return;
|
|
143
|
+
let t = window.__breadcrumbRegistry;
|
|
144
|
+
return t?.register(k, e), () => t?.unregister(k);
|
|
145
|
+
}, [k, J?.name]);
|
|
146
|
+
let [Y, X] = b({
|
|
140
147
|
name: "",
|
|
141
148
|
slug: "",
|
|
142
149
|
shortDescription: "",
|
|
@@ -154,7 +161,7 @@ var O = () => {
|
|
|
154
161
|
githubUrl: "",
|
|
155
162
|
requiresApproval: !1
|
|
156
163
|
});
|
|
157
|
-
|
|
164
|
+
y(() => {
|
|
158
165
|
J && (X({
|
|
159
166
|
name: J.name,
|
|
160
167
|
slug: J.slug,
|
|
@@ -257,7 +264,7 @@ var O = () => {
|
|
|
257
264
|
children: /* @__PURE__ */ D("div", {
|
|
258
265
|
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
259
266
|
children: [
|
|
260
|
-
/* @__PURE__ */ E(
|
|
267
|
+
/* @__PURE__ */ E(S, {
|
|
261
268
|
size: 48,
|
|
262
269
|
className: "text-destructive mb-4"
|
|
263
270
|
}),
|
|
@@ -277,14 +284,14 @@ var O = () => {
|
|
|
277
284
|
backLink: `${O}/apps`,
|
|
278
285
|
backLabel: N("devportal.backToApplications", "Back to Applications"),
|
|
279
286
|
children: /* @__PURE__ */ E(n, {
|
|
280
|
-
icon: /* @__PURE__ */ E(
|
|
287
|
+
icon: /* @__PURE__ */ E(w, { size: 48 }),
|
|
281
288
|
title: N("devportal.applicationNotFound", "Application not found"),
|
|
282
289
|
description: N("devportal.applicationNotFoundDescription", "The application you're looking for doesn't exist or you don't have access to it.")
|
|
283
290
|
})
|
|
284
291
|
});
|
|
285
292
|
let we = J.status === "DRAFT" || J.status === "REJECTED", Te = J.status === "PUBLISHED", Ee = J.status === "ACTIVE" || J.status === "PUBLISHED";
|
|
286
|
-
return /* @__PURE__ */ E(
|
|
287
|
-
permission:
|
|
293
|
+
return /* @__PURE__ */ E(g, {
|
|
294
|
+
permission: _.APP_UPDATE,
|
|
288
295
|
children: /* @__PURE__ */ D(r, {
|
|
289
296
|
title: `${N("devportal.editAppTitlePrefix", "Edit:")} ${J.name}`,
|
|
290
297
|
description: N("devportal.editAppDescription", "Update your application settings"),
|
|
@@ -304,7 +311,7 @@ var O = () => {
|
|
|
304
311
|
children: [
|
|
305
312
|
J.status === "PUBLISHED" && /* @__PURE__ */ D("div", {
|
|
306
313
|
className: "rounded-lg border border-border-subtle bg-status-warning-bg-subtle p-4 flex gap-3",
|
|
307
|
-
children: [/* @__PURE__ */ E(
|
|
314
|
+
children: [/* @__PURE__ */ E(C, {
|
|
308
315
|
size: 20,
|
|
309
316
|
className: "text-status-warning-text flex-shrink-0 mt-0.5"
|
|
310
317
|
}), /* @__PURE__ */ D("div", {
|
|
@@ -389,7 +396,8 @@ var O = () => {
|
|
|
389
396
|
label: N("devportal.visibility", "Visibility"),
|
|
390
397
|
value: Y.visibility,
|
|
391
398
|
onChange: (e) => Q("visibility", e.target.value),
|
|
392
|
-
options: de
|
|
399
|
+
options: de,
|
|
400
|
+
hint: N("devportal.visibilityHint", "Controls who can see this app once published. Public apps still require admin review and approval before appearing in the store.")
|
|
393
401
|
})
|
|
394
402
|
]
|
|
395
403
|
}),
|