@burdenoff/microfe-devportal 2026.1113.1 → 2026.1113.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/DevportalRoot.js +65 -119
- package/dist/devportal/DevportalRoot.js.map +1 -1
- package/dist/devportal/components/AppCard.js +4 -4
- package/dist/devportal/components/DeveloperSettingsHub.d.ts +6 -0
- package/dist/devportal/components/DeveloperSettingsHub.js +59 -0
- package/dist/devportal/components/DeveloperSettingsHub.js.map +1 -0
- package/dist/devportal/components/SectionNav.d.ts +1 -1
- package/dist/devportal/components/SectionNav.js +60 -36
- package/dist/devportal/components/SectionNav.js.map +1 -1
- package/dist/devportal/pages/AnalyticsDashboardPage.js +186 -179
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +113 -111
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +80 -84
- package/dist/devportal/pages/AppLogsPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +529 -504
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +68 -67
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +163 -166
- package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +134 -130
- package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
- package/dist/devportal/pages/DelegationsPage.js +11 -11
- package/dist/devportal/pages/DocumentationPage.js +100 -94
- package/dist/devportal/pages/DocumentationPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +379 -377
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/InvitationDecisionPage.js +83 -79
- package/dist/devportal/pages/InvitationDecisionPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientDetailPage.js +76 -76
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +126 -124
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationDetailPage.js +19 -19
- package/dist/devportal/pages/OrganizationMembersPage.js +223 -204
- package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
- package/dist/devportal/pages/ResourcesPage.d.ts +6 -0
- package/dist/devportal/pages/ResourcesPage.js +171 -155
- package/dist/devportal/pages/ResourcesPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js +13 -13
- package/dist/devportal/pages/SellerItemsPage.js +4 -4
- package/dist/devportal/pages/SettingsPage.js +330 -315
- package/dist/devportal/pages/SettingsPage.js.map +1 -1
- package/dist/devportal/pages/SetupPage.js +5 -5
- package/dist/devportal/pages/WebhookDetailPage.js +6 -6
- package/dist/devportal/pages/WebhooksPage.js +39 -39
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/dist/devportal/utils/appDetailTabs.d.ts +73 -0
- package/dist/devportal/utils/appDetailTabs.js +62 -0
- package/dist/devportal/utils/appDetailTabs.js.map +1 -0
- package/dist/devportal/utils/developerNav.d.ts +39 -0
- package/dist/devportal/utils/developerNav.js +304 -0
- package/dist/devportal/utils/developerNav.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebhooksPage.js","names":["EmptyState","EmptyState$1","mapWebhook","webhook","WebhooksPage","useParams","useDevportal","useI18n","tr","value","t","key","fallback","createPerm","useDevportalPermission","DEVPORTAL_PERMISSIONS","searchQuery","setSearchQuery","useState","statusFilter","setStatusFilter","singleAppData","useGetApplicationQuery","appId","name","reg","appsData","appsLoading","appsError","refetchApps","useGetApplicationsQuery","useGetWebhooksQuery","webhooks","data","allApplications","edge","applications","activeOrgId","a","handleNavigate","basePath","path","filteredWebhooks","matchesSearch","matchesStatus","getStatusIcon","status","CheckCircle","PauseCircle","XCircle","AlertTriangle","calculateSuccessRate","total","filteredApplications","normalizedQuery","application","PermissionGuard","PageLayout","Search","event","Loader2","AlertCircle","getErrorMessage","AppListItem","AppWindow","Plus","loading","error","refetch","columns","cn","Webhook","StatusBadge","rate","formatRelativeTime","stats","w","EmphasisPanel","GlassCard","DataTable","date","dateString","diffMs","diffMins","diffHours","diffDays"],"sources":["../../../src/devportal/pages/WebhooksPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Webhook,\n CheckCircle,\n XCircle,\n PauseCircle,\n AlertTriangle,\n Loader2,\n AlertCircle,\n AppWindow,\n} from \"lucide-react\";\nimport { useParams } from \"react-router\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { PermissionGuard } from \"../components\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type {\n Application,\n Webhook as WebhookType,\n WebhookStatus,\n} from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useGetApplicationsQuery,\n useGetApplicationQuery,\n useGetWebhooksQuery,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type DevportalWebhookFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Map GraphQL webhook to local Webhook type\n */\nfunction mapWebhook(webhook: DevportalWebhookFieldsFragment): WebhookType {\n return {\n id: webhook.id,\n applicationId: webhook.applicationId,\n name: webhook.name,\n description: webhook.description ?? undefined,\n url: webhook.url,\n secret: undefined,\n events: webhook.events ?? [],\n isActive: webhook.isActive,\n status: webhook.status as WebhookStatus,\n maxRetries: webhook.maxRetries ?? 3,\n retryDelay: webhook.retryDelay ?? 60,\n retryEnabled: webhook.retryEnabled ?? true,\n lastTriggeredAt: webhook.lastTriggeredAt ?? undefined,\n lastSuccessAt: webhook.lastSuccessAt ?? undefined,\n lastFailureAt: webhook.lastFailureAt ?? undefined,\n lastFailureReason: webhook.lastFailureReason ?? undefined,\n failureCount: webhook.failureCount ?? 0,\n successCount: webhook.successCount ?? 0,\n createdAt: webhook.createdAt,\n updatedAt: webhook.updatedAt,\n };\n}\n\n/**\n * Webhooks listing page\n *\n * Displays webhooks for a specific application.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const WebhooksPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\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(\n DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE,\n );\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<WebhookStatus | \"\">(\"\");\n\n // Fetch app name for breadcrumb when viewing webhooks for a specific app\n const { data: singleAppData } = useGetApplicationQuery({\n variables: { id: appId ?? \"\" },\n skip: !appId,\n fetchPolicy: \"cache-first\",\n });\n\n useEffect(() => {\n const name = singleAppData?.application?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, singleAppData?.application?.name]);\n\n const {\n data: appsData,\n loading: appsLoading,\n error: appsError,\n refetch: refetchApps,\n } = useGetApplicationsQuery({\n variables: { limit: 100 },\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch webhooks from GraphQL API (only if we have an applicationId)\n const { data, loading, error, refetch } = useGetWebhooksQuery({\n variables: {\n applicationId: appId || \"\",\n },\n skip: !appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const webhooks = data?.webhooks?.map(mapWebhook) ?? [];\n const allApplications: Application[] =\n appsData?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node as Application,\n ) ?? [];\n // Scope app picker to active org\n const applications = activeOrgId\n ? allApplications.filter((a) => a.developerOrganizationId === activeOrgId)\n : allApplications;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Filter webhooks\n const filteredWebhooks = webhooks.filter((webhook: WebhookType) => {\n const matchesSearch =\n !searchQuery ||\n webhook.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n webhook.url.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || webhook.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n\n const getStatusIcon = (status: WebhookStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return <CheckCircle size={16} className=\"text-status-success-text\" />;\n case \"PAUSED\":\n return <PauseCircle size={16} className=\"text-status-warning-text\" />;\n case \"FAILED\":\n return <XCircle size={16} className=\"text-status-error-text\" />;\n case \"DISABLED\":\n return <AlertTriangle size={16} className=\"text-text-tertiary\" />;\n default:\n return null;\n }\n };\n\n const calculateSuccessRate = (webhook: WebhookType): number => {\n const total = webhook.successCount + webhook.failureCount;\n if (total === 0) return 100;\n return Math.round((webhook.successCount / total) * 100);\n };\n\n // If no applicationId, show prompt to select an application\n if (!appId) {\n const filteredApplications = applications.filter((application) => {\n if (!searchQuery) return true;\n\n const normalizedQuery = searchQuery.toLowerCase();\n return (\n application.name.toLowerCase().includes(normalizedQuery) ||\n (application.slug ?? \"\").toLowerCase().includes(normalizedQuery)\n );\n });\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApps\",\n \"Manage webhooks for your applications\",\n )}\n purpose={tr(\n \"devportal.webhooksPickerPurpose\",\n \"Webhooks push real-time events (installs, payments, app updates) to a URL you host, so your backend can react without polling. Pick an application to view, add, or test the webhook endpoints it delivers to.\",\n )}\n >\n <div className=\"space-y-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.webhooksSelectAppHint\",\n \"Select an application below to view and manage its webhooks.\",\n )}\n </p>\n <div className=\"max-w-md\">\n <div className=\"relative\">\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=\"Search applications...\"\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.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 </div>\n\n {appsLoading ? (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n ) : appsError ? (\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 Failed to load applications\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(appsError)}\n </p>\n <button\n onClick={() => refetchApps()}\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 Try Again\n </button>\n </div>\n ) : filteredApplications.length > 0 ? (\n <div className=\"space-y-2\">\n {filteredApplications.map((application) => (\n <AppListItem\n key={application.id}\n app={application}\n onClick={() =>\n handleNavigate(`/apps/${application.id}/webhooks`)\n }\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration={searchQuery ? \"empty-search\" : \"empty-data\"}\n icon={<AppWindow size={32} />}\n title=\"No applications available\"\n description={\n searchQuery\n ? \"No applications matched your search.\"\n : \"Create an application before configuring webhooks.\"\n }\n action={\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 Create Application\n </button>\n }\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\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.loadingWebhooks\", \"Loading webhooks...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\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(\"devportal.failedToLoadWebhooks\", \"Failed to load webhooks\")}\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 const columns = [\n {\n key: \"name\",\n header: \"Webhook\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n webhook.status === \"ACTIVE\"\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : webhook.status === \"FAILED\"\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : webhook.status === \"PAUSED\"\n ? \"bg-status-warning-bg-subtle text-status-warning-text\"\n : \"bg-bg-sunken text-text-tertiary\",\n )}\n >\n <Webhook size={20} />\n </div>\n <div>\n <p className=\"font-medium\">{webhook.name}</p>\n <p className=\"text-xs text-text-secondary truncate max-w-xs\">\n {webhook.url}\n </p>\n </div>\n </div>\n ),\n },\n {\n key: \"events\",\n header: \"Events\",\n render: (webhook: WebhookType) => (\n <div className=\"flex flex-wrap gap-1\">\n {webhook.events.slice(0, 2).map((event) => (\n <span\n key={event}\n className=\"inline-flex rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {event.split(\".\")[0]}\n </span>\n ))}\n {webhook.events.length > 2 && (\n <span className=\"text-xs text-text-secondary\">\n +{webhook.events.length - 2} more\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-2\">\n {getStatusIcon(webhook.status)}\n <StatusBadge status={webhook.status} type=\"webhook\" />\n </div>\n ),\n },\n {\n key: \"successRate\",\n header: \"Success Rate\",\n render: (webhook: WebhookType) => {\n const rate = calculateSuccessRate(webhook);\n return (\n <div className=\"w-24\">\n <div className=\"flex items-center justify-between text-xs mb-1\">\n <span\n className={cn(\n rate >= 95\n ? \"text-status-success-text\"\n : rate >= 80\n ? \"text-status-warning-text\"\n : \"text-status-error-text\",\n )}\n >\n {rate}%\n </span>\n </div>\n <div className=\"h-1.5 rounded-full bg-bg-sunken\">\n <div\n className={cn(\n \"h-full rounded-full transition-all\",\n rate >= 95\n ? \"bg-status-success-bg-subtle\"\n : rate >= 80\n ? \"bg-status-warning-bg-subtle\"\n : \"bg-status-error-bg-subtle\",\n )}\n style={{ width: `${rate}%` }}\n />\n </div>\n </div>\n );\n },\n },\n {\n key: \"deliveries\",\n header: \"Deliveries\",\n render: (webhook: WebhookType) => (\n <div className=\"text-sm\">\n <span className=\"text-status-success-text\">\n {webhook.successCount.toLocaleString()}\n </span>\n <span className=\"text-text-secondary\"> / </span>\n <span className=\"text-status-error-text\">\n {webhook.failureCount.toLocaleString()}\n </span>\n </div>\n ),\n },\n {\n key: \"lastTriggered\",\n header: \"Last Triggered\",\n render: (webhook: WebhookType) => (\n <span className=\"text-sm text-text-secondary\">\n {webhook.lastTriggeredAt\n ? formatRelativeTime(webhook.lastTriggeredAt)\n : \"Never\"}\n </span>\n ),\n },\n ];\n\n // Stats calculation\n const stats = {\n total: webhooks.length,\n active: webhooks.filter((w: WebhookType) => w.status === \"ACTIVE\").length,\n failed: webhooks.filter((w: WebhookType) => w.status === \"FAILED\").length,\n paused: webhooks.filter((w: WebhookType) => w.status === \"PAUSED\").length,\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n purpose={tr(\n \"devportal.webhooksAppPurpose\",\n \"These webhooks deliver this application's events to your endpoints. Add a URL, choose which events to subscribe to, and send a test delivery to confirm your server receives and verifies the signed payload.\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/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.createWebhook\", \"Create Webhook\")}\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=\"Search by name or URL...\"\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 WebhookStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"PAUSED\">Paused</option>\n <option value=\"FAILED\">Failed</option>\n <option value=\"DISABLED\">Disabled</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Webhook size={16} />\n <span className=\"text-sm\">Total Webhooks</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <CheckCircle size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <PauseCircle size={16} />\n <span className=\"text-sm\">Paused</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.paused}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <XCircle size={16} />\n <span className=\"text-sm\">Failed</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.failed}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredWebhooks.length} webhook\n {filteredWebhooks.length !== 1 ? \"s\" : \"\"} found\n </p>\n\n {/* Webhooks Table */}\n {filteredWebhooks.length > 0 ? (\n <DataTable\n data={filteredWebhooks}\n columns={columns}\n keyField=\"id\"\n onRowClick={(webhook) =>\n handleNavigate(`/apps/${appId}/webhooks/${webhook.id}`)\n }\n />\n ) : (\n <EmptyState\n illustration=\"empty-notifications\"\n title={tr(\"devportal.noWebhooksFound\", \"No webhooks found\")}\n description={\n searchQuery || statusFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstWebhook\",\n \"Create your first webhook to receive real-time event notifications\",\n )\n }\n action={\n !searchQuery && !statusFilter ? (\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/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.createWebhook\", \"Create Webhook\")}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\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 diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n\n if (diffMins < 1) return \"Just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebhooksPage.js","names":["EmptyState","EmptyState$1","mapWebhook","webhook","WebhooksPage","useParams","useDevportal","useI18n","tr","value","t","key","fallback","createPerm","useDevportalPermission","DEVPORTAL_PERMISSIONS","searchQuery","setSearchQuery","useState","statusFilter","setStatusFilter","singleAppData","useGetApplicationQuery","appId","name","reg","appsData","appsLoading","appsError","refetchApps","useGetApplicationsQuery","useGetWebhooksQuery","webhooks","data","allApplications","edge","applications","activeOrgId","a","handleNavigate","basePath","path","filteredWebhooks","matchesSearch","matchesStatus","getStatusIcon","status","CheckCircle","PauseCircle","XCircle","AlertTriangle","calculateSuccessRate","total","filteredApplications","normalizedQuery","application","PermissionGuard","PageLayout","Search","event","Loader2","AlertCircle","getErrorMessage","AppListItem","AppWindow","Plus","loading","error","refetch","columns","cn","Webhook","StatusBadge","rate","formatRelativeTime","stats","w","EmphasisPanel","GlassCard","DataTable","date","dateString","diffMs","diffMins","diffHours","diffDays"],"sources":["../../../src/devportal/pages/WebhooksPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Webhook,\n CheckCircle,\n XCircle,\n PauseCircle,\n AlertTriangle,\n Loader2,\n AlertCircle,\n AppWindow,\n} from \"lucide-react\";\nimport { useParams } from \"react-router\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { PermissionGuard } from \"../components\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type {\n Application,\n Webhook as WebhookType,\n WebhookStatus,\n} from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useGetApplicationsQuery,\n useGetApplicationQuery,\n useGetWebhooksQuery,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type DevportalWebhookFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Map GraphQL webhook to local Webhook type\n */\nfunction mapWebhook(webhook: DevportalWebhookFieldsFragment): WebhookType {\n return {\n id: webhook.id,\n applicationId: webhook.applicationId,\n name: webhook.name,\n description: webhook.description ?? undefined,\n url: webhook.url,\n secret: undefined,\n events: webhook.events ?? [],\n isActive: webhook.isActive,\n status: webhook.status as WebhookStatus,\n maxRetries: webhook.maxRetries ?? 3,\n retryDelay: webhook.retryDelay ?? 60,\n retryEnabled: webhook.retryEnabled ?? true,\n lastTriggeredAt: webhook.lastTriggeredAt ?? undefined,\n lastSuccessAt: webhook.lastSuccessAt ?? undefined,\n lastFailureAt: webhook.lastFailureAt ?? undefined,\n lastFailureReason: webhook.lastFailureReason ?? undefined,\n failureCount: webhook.failureCount ?? 0,\n successCount: webhook.successCount ?? 0,\n createdAt: webhook.createdAt,\n updatedAt: webhook.updatedAt,\n };\n}\n\n/**\n * Webhooks listing page\n *\n * Displays webhooks for a specific application.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const WebhooksPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\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(\n DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE,\n );\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<WebhookStatus | \"\">(\"\");\n\n // Fetch app name for breadcrumb when viewing webhooks for a specific app\n const { data: singleAppData } = useGetApplicationQuery({\n variables: { id: appId ?? \"\" },\n skip: !appId,\n fetchPolicy: \"cache-first\",\n });\n\n useEffect(() => {\n const name = singleAppData?.application?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, singleAppData?.application?.name]);\n\n const {\n data: appsData,\n loading: appsLoading,\n error: appsError,\n refetch: refetchApps,\n } = useGetApplicationsQuery({\n variables: { limit: 100 },\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch webhooks from GraphQL API (only if we have an applicationId)\n const { data, loading, error, refetch } = useGetWebhooksQuery({\n variables: {\n applicationId: appId || \"\",\n },\n skip: !appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const webhooks = data?.webhooks?.map(mapWebhook) ?? [];\n const allApplications: Application[] =\n appsData?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node as Application,\n ) ?? [];\n // Scope app picker to active org\n const applications = activeOrgId\n ? allApplications.filter((a) => a.developerOrganizationId === activeOrgId)\n : allApplications;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Filter webhooks\n const filteredWebhooks = webhooks.filter((webhook: WebhookType) => {\n const matchesSearch =\n !searchQuery ||\n webhook.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n webhook.url.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || webhook.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n\n const getStatusIcon = (status: WebhookStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return <CheckCircle size={16} className=\"text-status-success-text\" />;\n case \"PAUSED\":\n return <PauseCircle size={16} className=\"text-status-warning-text\" />;\n case \"FAILED\":\n return <XCircle size={16} className=\"text-status-error-text\" />;\n case \"DISABLED\":\n return <AlertTriangle size={16} className=\"text-text-tertiary\" />;\n default:\n return null;\n }\n };\n\n const calculateSuccessRate = (webhook: WebhookType): number => {\n const total = webhook.successCount + webhook.failureCount;\n if (total === 0) return 100;\n return Math.round((webhook.successCount / total) * 100);\n };\n\n // If no applicationId, show prompt to select an application\n if (!appId) {\n const filteredApplications = applications.filter((application) => {\n if (!searchQuery) return true;\n\n const normalizedQuery = searchQuery.toLowerCase();\n return (\n application.name.toLowerCase().includes(normalizedQuery) ||\n (application.slug ?? \"\").toLowerCase().includes(normalizedQuery)\n );\n });\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApps\",\n \"Manage webhooks for your applications\",\n )}\n purpose={tr(\n \"devportal.webhooksPickerPurpose\",\n \"Webhooks push real-time events (installs, payments, app updates) to a URL you host, so your backend can react without polling. Pick an application to view, add, or test the webhook endpoints it delivers to.\",\n )}\n >\n <div className=\"space-y-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.webhooksSelectAppHint\",\n \"Select an application below to view and manage its webhooks.\",\n )}\n </p>\n <div className=\"max-w-md\">\n <div className=\"relative\">\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=\"Search applications...\"\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.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 </div>\n\n {appsLoading ? (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n ) : appsError ? (\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 Failed to load applications\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(appsError)}\n </p>\n <button\n onClick={() => refetchApps()}\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 Try Again\n </button>\n </div>\n ) : filteredApplications.length > 0 ? (\n <div className=\"space-y-2\">\n {filteredApplications.map((application) => (\n <AppListItem\n key={application.id}\n app={application}\n onClick={() =>\n handleNavigate(`/apps/${application.id}/webhooks`)\n }\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration={searchQuery ? \"empty-search\" : \"empty-data\"}\n icon={<AppWindow size={32} />}\n title=\"No applications available\"\n description={\n searchQuery\n ? \"No applications matched your search.\"\n : \"Create an application before configuring webhooks.\"\n }\n action={\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 Create Application\n </button>\n }\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\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.loadingWebhooks\", \"Loading webhooks...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\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(\"devportal.failedToLoadWebhooks\", \"Failed to load webhooks\")}\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 const columns = [\n {\n key: \"name\",\n header: \"Webhook\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n webhook.status === \"ACTIVE\"\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : webhook.status === \"FAILED\"\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : webhook.status === \"PAUSED\"\n ? \"bg-status-warning-bg-subtle text-status-warning-text\"\n : \"bg-bg-sunken text-text-tertiary\",\n )}\n >\n <Webhook size={20} />\n </div>\n <div>\n <p className=\"font-medium\">{webhook.name}</p>\n <p className=\"text-xs text-text-secondary truncate max-w-xs\">\n {webhook.url}\n </p>\n </div>\n </div>\n ),\n },\n {\n key: \"events\",\n header: \"Events\",\n render: (webhook: WebhookType) => (\n <div className=\"flex flex-wrap gap-1\">\n {webhook.events.slice(0, 2).map((event) => (\n <span\n key={event}\n className=\"inline-flex rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {event.split(\".\")[0]}\n </span>\n ))}\n {webhook.events.length > 2 && (\n <span className=\"text-xs text-text-secondary\">\n +{webhook.events.length - 2} more\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-2\">\n {getStatusIcon(webhook.status)}\n <StatusBadge status={webhook.status} type=\"webhook\" />\n </div>\n ),\n },\n {\n key: \"successRate\",\n header: \"Success Rate\",\n render: (webhook: WebhookType) => {\n const rate = calculateSuccessRate(webhook);\n return (\n <div className=\"w-24\">\n <div className=\"flex items-center justify-between text-xs mb-1\">\n <span\n className={cn(\n rate >= 95\n ? \"text-status-success-text\"\n : rate >= 80\n ? \"text-status-warning-text\"\n : \"text-status-error-text\",\n )}\n >\n {rate}%\n </span>\n </div>\n <div className=\"h-1.5 rounded-full bg-bg-sunken\">\n <div\n className={cn(\n \"h-full rounded-full transition-all\",\n rate >= 95\n ? \"bg-status-success-bg-subtle\"\n : rate >= 80\n ? \"bg-status-warning-bg-subtle\"\n : \"bg-status-error-bg-subtle\",\n )}\n style={{ width: `${rate}%` }}\n />\n </div>\n </div>\n );\n },\n },\n {\n key: \"deliveries\",\n header: \"Deliveries\",\n render: (webhook: WebhookType) => (\n <div className=\"text-sm\">\n <span className=\"text-status-success-text\">\n {webhook.successCount.toLocaleString()}\n </span>\n <span className=\"text-text-secondary\"> / </span>\n <span className=\"text-status-error-text\">\n {webhook.failureCount.toLocaleString()}\n </span>\n </div>\n ),\n },\n {\n key: \"lastTriggered\",\n header: \"Last Triggered\",\n render: (webhook: WebhookType) => (\n <span className=\"text-sm text-text-secondary\">\n {webhook.lastTriggeredAt\n ? formatRelativeTime(webhook.lastTriggeredAt)\n : \"Never\"}\n </span>\n ),\n },\n ];\n\n // Stats calculation\n const stats = {\n total: webhooks.length,\n active: webhooks.filter((w: WebhookType) => w.status === \"ACTIVE\").length,\n failed: webhooks.filter((w: WebhookType) => w.status === \"FAILED\").length,\n paused: webhooks.filter((w: WebhookType) => w.status === \"PAUSED\").length,\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n purpose={tr(\n \"devportal.webhooksAppPurpose\",\n \"These webhooks deliver this application's events to your endpoints. Add a URL, choose which events to subscribe to, and send a test delivery to confirm your server receives and verifies the signed payload.\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/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.createWebhook\", \"Create Webhook\")}\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=\"Search by name or URL...\"\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 WebhookStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"PAUSED\">Paused</option>\n <option value=\"FAILED\">Failed</option>\n <option value=\"DISABLED\">Disabled</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Webhook size={16} />\n <span className=\"text-sm\">Total Webhooks</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <CheckCircle size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <PauseCircle size={16} />\n <span className=\"text-sm\">Paused</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.paused}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <XCircle size={16} />\n <span className=\"text-sm\">Failed</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.failed}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredWebhooks.length} webhook\n {filteredWebhooks.length !== 1 ? \"s\" : \"\"} found\n </p>\n\n {/* Webhooks Table */}\n {filteredWebhooks.length > 0 ? (\n <DataTable\n data={filteredWebhooks}\n columns={columns}\n keyField=\"id\"\n onRowClick={(webhook) =>\n handleNavigate(`/apps/${appId}/webhooks/${webhook.id}`)\n }\n />\n ) : (\n <EmptyState\n illustration=\"empty-notifications\"\n title={tr(\"devportal.noWebhooksFound\", \"No webhooks found\")}\n description={\n searchQuery || statusFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstWebhook\",\n \"Create your first webhook to receive real-time event notifications\",\n )\n }\n action={\n !searchQuery && !statusFilter ? (\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/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.createWebhook\", \"Create Webhook\")}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\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 diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n\n if (diffMins < 1) return \"Just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decisions for the App detail tabs and the Deactivate wording (BOFF-11862).
|
|
3
|
+
* Kept free of React so the rules can be unit tested.
|
|
4
|
+
*/
|
|
5
|
+
export type AppDetailTabId = "overview" | "permissions" | "webhooks" | "delegations" | "analytics" | "playground" | "store-listing" | "versions" | "pricing" | "oauth" | "api-keys";
|
|
6
|
+
export interface AppDetailTabFacts {
|
|
7
|
+
/** NODE/WORKFLOW/PARSER/WIDGET/DASHBOARD: manifest driven, no OAuth or webhooks. */
|
|
8
|
+
isPackageApp: boolean;
|
|
9
|
+
isIntegrationApp: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Tabs that stay in the row in the FluidGrids variant, in order. */
|
|
12
|
+
export declare const FLUIDGRIDS_PRIMARY_TABS: readonly AppDetailTabId[];
|
|
13
|
+
/** Tabs folded under "More" in the FluidGrids variant, in order. */
|
|
14
|
+
export declare const FLUIDGRIDS_OVERFLOW_TABS: readonly AppDetailTabId[];
|
|
15
|
+
/**
|
|
16
|
+
* Stub tabs that only say "now managed centrally". The FluidGrids variant drops
|
|
17
|
+
* them; the same screens stay reachable at `/oauth` and `/api-keys`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const STUB_TABS: readonly AppDetailTabId[];
|
|
20
|
+
/** Which of `all` apply to this app. The rules every app has today. */
|
|
21
|
+
export declare function applicableTabs(all: readonly AppDetailTabId[], facts: AppDetailTabFacts): AppDetailTabId[];
|
|
22
|
+
export interface AppDetailTabLayout {
|
|
23
|
+
/** Every tab shown, primary ones first. Never loses a tab the app has. */
|
|
24
|
+
ordered: AppDetailTabId[];
|
|
25
|
+
/** How many of `ordered` sit in the row before "More". */
|
|
26
|
+
primaryCount: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Tab order for one app. Off the variant: today's order, no overflow
|
|
30
|
+
* (`primaryCount` is the full length). On the variant: stub tabs dropped,
|
|
31
|
+
* primary tabs first, the rest in the "More" fold.
|
|
32
|
+
*/
|
|
33
|
+
export declare function layoutAppDetailTabs(all: readonly AppDetailTabId[], facts: AppDetailTabFacts, fluidgrids: boolean): AppDetailTabLayout;
|
|
34
|
+
/**
|
|
35
|
+
* The tab named by `?tab=`. A tab the app does not have (or a retired stub tab
|
|
36
|
+
* in the variant) falls back to the first one rather than showing nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveTabParam(param: string | null, available: readonly AppDetailTabId[]): AppDetailTabId;
|
|
39
|
+
/** Query-only link target for a tab, e.g. `?tab=versions`. */
|
|
40
|
+
export declare function tabHref(id: AppDetailTabId): string;
|
|
41
|
+
export interface CopyEntry {
|
|
42
|
+
key: string;
|
|
43
|
+
fallback: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Deactivate wording shared by App detail, Edit app and the My apps bulk
|
|
47
|
+
* action in the FluidGrids variant. What the mutation does (global-devportal-svc
|
|
48
|
+
* `deactivateApplication`): status becomes INACTIVE, the auth app is switched off
|
|
49
|
+
* (tokens stop working), the store listing is delisted. No portal screen
|
|
50
|
+
* reactivates an app, so the text says it cannot be undone from here.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEACTIVATE_COPY: {
|
|
53
|
+
readonly action: {
|
|
54
|
+
readonly key: "devportal.fluidgrids.deactivateApp";
|
|
55
|
+
readonly fallback: "Deactivate app";
|
|
56
|
+
};
|
|
57
|
+
readonly title: {
|
|
58
|
+
readonly key: "devportal.fluidgrids.deactivateAppTitle";
|
|
59
|
+
readonly fallback: "Deactivate this app?";
|
|
60
|
+
};
|
|
61
|
+
readonly description: {
|
|
62
|
+
readonly key: "devportal.fluidgrids.deactivateAppDescription";
|
|
63
|
+
readonly fallback: "Deactivate \"{{name}}\"? The app can no longer sign in or call the API, and it is removed from the store. This cannot be undone from the Developer Portal.";
|
|
64
|
+
};
|
|
65
|
+
readonly bulkDescription: {
|
|
66
|
+
readonly key: "devportal.fluidgrids.deactivateAppsDescription";
|
|
67
|
+
readonly fallback: "Deactivate {{count}} selected app(s)? Each one can no longer sign in or call the API and is removed from the store. This cannot be undone from the Developer Portal.";
|
|
68
|
+
};
|
|
69
|
+
readonly dangerZone: {
|
|
70
|
+
readonly key: "devportal.fluidgrids.dangerZoneWarning";
|
|
71
|
+
readonly fallback: "Deactivating or deleting an app cannot be undone from the Developer Portal. Please proceed with caution.";
|
|
72
|
+
};
|
|
73
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/devportal/utils/appDetailTabs.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"overview",
|
|
4
|
+
"store-listing",
|
|
5
|
+
"versions",
|
|
6
|
+
"analytics"
|
|
7
|
+
], t = [
|
|
8
|
+
"pricing",
|
|
9
|
+
"permissions",
|
|
10
|
+
"webhooks",
|
|
11
|
+
"delegations",
|
|
12
|
+
"playground"
|
|
13
|
+
], n = ["oauth", "api-keys"];
|
|
14
|
+
function r(e, t) {
|
|
15
|
+
return e.filter((e) => {
|
|
16
|
+
let n = e === "permissions" || e === "webhooks" || e === "delegations" || e === "playground" || e === "oauth" || e === "api-keys";
|
|
17
|
+
return !(t.isPackageApp && n || !t.isPackageApp && !t.isIntegrationApp && e === "versions" || !t.isIntegrationApp && (e === "oauth" || e === "api-keys"));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function i(i, a, o) {
|
|
21
|
+
let s = r(i, a);
|
|
22
|
+
if (!o) return {
|
|
23
|
+
ordered: s,
|
|
24
|
+
primaryCount: s.length
|
|
25
|
+
};
|
|
26
|
+
let c = s.filter((e) => !n.includes(e)), l = e.filter((e) => c.includes(e)), u = c.filter((e) => !l.includes(e)), d = [...t.filter((e) => u.includes(e)), ...u.filter((e) => !t.includes(e))];
|
|
27
|
+
return {
|
|
28
|
+
ordered: [...l, ...d],
|
|
29
|
+
primaryCount: l.length
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function a(e, t) {
|
|
33
|
+
return t.find((t) => t === e) ?? t[0] ?? "overview";
|
|
34
|
+
}
|
|
35
|
+
function o(e) {
|
|
36
|
+
return `?tab=${e}`;
|
|
37
|
+
}
|
|
38
|
+
var s = {
|
|
39
|
+
action: {
|
|
40
|
+
key: "devportal.fluidgrids.deactivateApp",
|
|
41
|
+
fallback: "Deactivate app"
|
|
42
|
+
},
|
|
43
|
+
title: {
|
|
44
|
+
key: "devportal.fluidgrids.deactivateAppTitle",
|
|
45
|
+
fallback: "Deactivate this app?"
|
|
46
|
+
},
|
|
47
|
+
description: {
|
|
48
|
+
key: "devportal.fluidgrids.deactivateAppDescription",
|
|
49
|
+
fallback: "Deactivate \"{{name}}\"? The app can no longer sign in or call the API, and it is removed from the store. This cannot be undone from the Developer Portal."
|
|
50
|
+
},
|
|
51
|
+
bulkDescription: {
|
|
52
|
+
key: "devportal.fluidgrids.deactivateAppsDescription",
|
|
53
|
+
fallback: "Deactivate {{count}} selected app(s)? Each one can no longer sign in or call the API and is removed from the store. This cannot be undone from the Developer Portal."
|
|
54
|
+
},
|
|
55
|
+
dangerZone: {
|
|
56
|
+
key: "devportal.fluidgrids.dangerZoneWarning",
|
|
57
|
+
fallback: "Deactivating or deleting an app cannot be undone from the Developer Portal. Please proceed with caution."
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
export { s as DEACTIVATE_COPY, t as FLUIDGRIDS_OVERFLOW_TABS, e as FLUIDGRIDS_PRIMARY_TABS, n as STUB_TABS, r as applicableTabs, i as layoutAppDetailTabs, a as resolveTabParam, o as tabHref };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=appDetailTabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appDetailTabs.js","names":["FLUIDGRIDS_PRIMARY_TABS","FLUIDGRIDS_OVERFLOW_TABS","STUB_TABS","applicableTabs","all","integrationOnly","id","facts","layoutAppDetailTabs","applicable","fluidgrids","kept","primary","rest","overflow","resolveTabParam","available","param","tabHref","DEACTIVATE_COPY"],"sources":["../../../src/devportal/utils/appDetailTabs.ts"],"sourcesContent":["/**\n * Pure decisions for the App detail tabs and the Deactivate wording (BOFF-11862).\n * Kept free of React so the rules can be unit tested.\n */\n\nexport type AppDetailTabId =\n | \"overview\"\n | \"permissions\"\n | \"webhooks\"\n | \"delegations\"\n | \"analytics\"\n | \"playground\"\n | \"store-listing\"\n | \"versions\"\n | \"pricing\"\n | \"oauth\"\n | \"api-keys\";\n\nexport interface AppDetailTabFacts {\n /** NODE/WORKFLOW/PARSER/WIDGET/DASHBOARD: manifest driven, no OAuth or webhooks. */\n isPackageApp: boolean;\n isIntegrationApp: boolean;\n}\n\n/** Tabs that stay in the row in the FluidGrids variant, in order. */\nexport const FLUIDGRIDS_PRIMARY_TABS: readonly AppDetailTabId[] = [\n \"overview\",\n \"store-listing\",\n \"versions\",\n \"analytics\",\n];\n\n/** Tabs folded under \"More\" in the FluidGrids variant, in order. */\nexport const FLUIDGRIDS_OVERFLOW_TABS: readonly AppDetailTabId[] = [\n \"pricing\",\n \"permissions\",\n \"webhooks\",\n \"delegations\",\n \"playground\",\n];\n\n/**\n * Stub tabs that only say \"now managed centrally\". The FluidGrids variant drops\n * them; the same screens stay reachable at `/oauth` and `/api-keys`.\n */\nexport const STUB_TABS: readonly AppDetailTabId[] = [\"oauth\", \"api-keys\"];\n\n/** Which of `all` apply to this app. The rules every app has today. */\nexport function applicableTabs(\n all: readonly AppDetailTabId[],\n facts: AppDetailTabFacts,\n): AppDetailTabId[] {\n return all.filter((id) => {\n const integrationOnly =\n id === \"permissions\" ||\n id === \"webhooks\" ||\n id === \"delegations\" ||\n id === \"playground\" ||\n id === \"oauth\" ||\n id === \"api-keys\";\n if (facts.isPackageApp && integrationOnly) return false;\n if (!facts.isPackageApp && !facts.isIntegrationApp && id === \"versions\") {\n return false;\n }\n if (!facts.isIntegrationApp && (id === \"oauth\" || id === \"api-keys\")) {\n return false;\n }\n return true;\n });\n}\n\nexport interface AppDetailTabLayout {\n /** Every tab shown, primary ones first. Never loses a tab the app has. */\n ordered: AppDetailTabId[];\n /** How many of `ordered` sit in the row before \"More\". */\n primaryCount: number;\n}\n\n/**\n * Tab order for one app. Off the variant: today's order, no overflow\n * (`primaryCount` is the full length). On the variant: stub tabs dropped,\n * primary tabs first, the rest in the \"More\" fold.\n */\nexport function layoutAppDetailTabs(\n all: readonly AppDetailTabId[],\n facts: AppDetailTabFacts,\n fluidgrids: boolean,\n): AppDetailTabLayout {\n const applicable = applicableTabs(all, facts);\n if (!fluidgrids) {\n return { ordered: applicable, primaryCount: applicable.length };\n }\n const kept = applicable.filter((id) => !STUB_TABS.includes(id));\n const primary = FLUIDGRIDS_PRIMARY_TABS.filter((id) => kept.includes(id));\n const rest = kept.filter((id) => !primary.includes(id));\n // Overflow tabs in their agreed order; anything unknown is still kept, at the end.\n const overflow = [\n ...FLUIDGRIDS_OVERFLOW_TABS.filter((id) => rest.includes(id)),\n ...rest.filter((id) => !FLUIDGRIDS_OVERFLOW_TABS.includes(id)),\n ];\n return { ordered: [...primary, ...overflow], primaryCount: primary.length };\n}\n\n/**\n * The tab named by `?tab=`. A tab the app does not have (or a retired stub tab\n * in the variant) falls back to the first one rather than showing nothing.\n */\nexport function resolveTabParam(\n param: string | null,\n available: readonly AppDetailTabId[],\n): AppDetailTabId {\n const hit = available.find((id) => id === param);\n return hit ?? available[0] ?? \"overview\";\n}\n\n/** Query-only link target for a tab, e.g. `?tab=versions`. */\nexport function tabHref(id: AppDetailTabId): string {\n return `?tab=${id}`;\n}\n\nexport interface CopyEntry {\n key: string;\n fallback: string;\n}\n\n/**\n * Deactivate wording shared by App detail, Edit app and the My apps bulk\n * action in the FluidGrids variant. What the mutation does (global-devportal-svc\n * `deactivateApplication`): status becomes INACTIVE, the auth app is switched off\n * (tokens stop working), the store listing is delisted. No portal screen\n * reactivates an app, so the text says it cannot be undone from here.\n */\nexport const DEACTIVATE_COPY = {\n action: {\n key: \"devportal.fluidgrids.deactivateApp\",\n fallback: \"Deactivate app\",\n },\n title: {\n key: \"devportal.fluidgrids.deactivateAppTitle\",\n fallback: \"Deactivate this app?\",\n },\n description: {\n key: \"devportal.fluidgrids.deactivateAppDescription\",\n fallback:\n 'Deactivate \"{{name}}\"? The app can no longer sign in or call the API, and it is removed from the store. This cannot be undone from the Developer Portal.',\n },\n bulkDescription: {\n key: \"devportal.fluidgrids.deactivateAppsDescription\",\n fallback:\n \"Deactivate {{count}} selected app(s)? Each one can no longer sign in or call the API and is removed from the store. This cannot be undone from the Developer Portal.\",\n },\n dangerZone: {\n key: \"devportal.fluidgrids.dangerZoneWarning\",\n fallback:\n \"Deactivating or deleting an app cannot be undone from the Developer Portal. Please proceed with caution.\",\n },\n} as const satisfies Record<string, CopyEntry>;\n"],"mappings":";AAyBA,IAAaA,IAAqD;CAChE;CACA;CACA;CACA;AACF,GAGaC,IAAsD;CACjE;CACA;CACA;CACA;CACA;AACF,GAMaC,IAAuC,CAAC,SAAS,UAAU;AAGxE,SAAgBC,EACd,GACA,GACkB;CAClB,OAAOC,EAAI,QAAQ,MAAO;EACxB,IAAMC,IACJC,MAAO,iBACPA,MAAO,cACPA,MAAO,iBACPA,MAAO,gBACPA,MAAO,WACPA,MAAO;EAQT,OAHA,EAJIC,EAAM,gBAAgBF,KACtB,CAACE,EAAM,gBAAgB,CAACA,EAAM,oBAAoBD,MAAO,cAGzD,CAACC,EAAM,qBAAqBD,MAAO,WAAWA,MAAO;CAI3D,CAAC;AACH;AAcA,SAAgBE,EACd,GACA,GACA,GACoB;CACpB,IAAMC,IAAaN,EAAeC,GAAKG,CAAK;CAC5C,IAAI,CAACG,GACH,OAAO;EAAE,SAASD;EAAY,cAAcA,EAAW;CAAO;CAEhE,IAAME,IAAOF,EAAW,QAAQ,MAAO,CAACP,EAAU,SAASI,CAAE,CAAC,GACxDM,IAAUZ,EAAwB,QAAQ,MAAOW,EAAK,SAASL,CAAE,CAAC,GAClEO,IAAOF,EAAK,QAAQ,MAAO,CAACC,EAAQ,SAASN,CAAE,CAAC,GAEhDQ,IAAW,CACf,GAAGb,EAAyB,QAAQ,MAAOY,EAAK,SAASP,CAAE,CAAC,GAC5D,GAAGO,EAAK,QAAQ,MAAO,CAACZ,EAAyB,SAASK,CAAE,CAAC,CAC/D;CACA,OAAO;EAAE,SAAS,CAAC,GAAGM,GAAS,GAAGE,CAAQ;EAAG,cAAcF,EAAQ;CAAO;AAC5E;AAMA,SAAgBG,EACd,GACA,GACgB;CAEhB,OADYC,EAAU,MAAM,MAAOV,MAAOW,CACnC,KAAOD,EAAU,MAAM;AAChC;AAGA,SAAgBE,EAAQ,GAA4B;CAClD,OAAO,QAAQZ;AACjB;AAcA,IAAaa,IAAkB;CAC7B,QAAQ;EACN,KAAK;EACL,UAAU;CACZ;CACA,OAAO;EACL,KAAK;EACL,UAAU;CACZ;CACA,aAAa;EACX,KAAK;EACL,UACE;CACJ;CACA,iBAAiB;EACf,KAAK;EACL,UACE;CACJ;CACA,YAAY;EACV,KAAK;EACL,UACE;CACJ;AACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu and hub structure for the Developer Portal, as plain data.
|
|
3
|
+
*
|
|
4
|
+
* FluidGrids (Navigation v2, BOFF-11863) gets one "Developer settings" area that
|
|
5
|
+
* lists API keys, OAuth apps, webhooks, delegations, app logs, analytics and
|
|
6
|
+
* documentation, instead of a long flat menu. Every other app keeps today's
|
|
7
|
+
* menus exactly: `getMenuEntries(false)` and `getSectionEntries(false)` return
|
|
8
|
+
* the legacy lists unchanged. Components map `id` to an icon and `i18nKey` to
|
|
9
|
+
* a translation; nothing here depends on React.
|
|
10
|
+
*/
|
|
11
|
+
export interface NavEntry {
|
|
12
|
+
id: string;
|
|
13
|
+
/** Path under the module's base path (no leading base), "" for the index. */
|
|
14
|
+
path: string;
|
|
15
|
+
/** Absolute shell path; wins over `path` and is not prefixed by the base path. */
|
|
16
|
+
absolutePath?: string;
|
|
17
|
+
i18nKey: string;
|
|
18
|
+
fallback: string;
|
|
19
|
+
order: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function getMenuEntries(isFluidgrids: boolean): readonly NavEntry[];
|
|
22
|
+
export declare function getSectionEntries(isFluidgrids: boolean): readonly NavEntry[];
|
|
23
|
+
export declare function getSectionMatchPrefixes(isFluidgrids: boolean, sectionId: string): readonly string[];
|
|
24
|
+
export interface DeveloperSettingsEntry {
|
|
25
|
+
id: string;
|
|
26
|
+
/** Path under the module's base path. */
|
|
27
|
+
path: string;
|
|
28
|
+
titleKey: string;
|
|
29
|
+
titleFallback: string;
|
|
30
|
+
descriptionKey: string;
|
|
31
|
+
descriptionFallback: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The Developer settings hub. One name per thing: "OAuth apps" are the sign-in
|
|
35
|
+
* credentials; "apps" on their own are the developer applications you publish.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DEVELOPER_SETTINGS_ENTRIES: readonly DeveloperSettingsEntry[];
|
|
38
|
+
/** Join a module base path and an entry path without doubling slashes. */
|
|
39
|
+
export declare function resolveEntryPath(basePath: string | undefined, entry: Pick<NavEntry, "path" | "absolutePath">): string;
|