@burdenoff/microfe-devportal 2026.626.1 → 2026.626.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/devportal/DevportalRoutes.js +38 -34
  2. package/dist/devportal/DevportalRoutes.js.map +1 -1
  3. package/dist/devportal/components/ConfirmDialog.js.map +1 -1
  4. package/dist/devportal/components/DataTable.js.map +1 -1
  5. package/dist/devportal/components/FormField.js.map +1 -1
  6. package/dist/devportal/components/GrantDelegationModal.js +2 -1
  7. package/dist/devportal/components/GrantDelegationModal.js.map +1 -1
  8. package/dist/devportal/components/LoadingState.js.map +1 -1
  9. package/dist/devportal/components/PageLayout.js +1 -1
  10. package/dist/devportal/components/PageLayout.js.map +1 -1
  11. package/dist/devportal/components/SelectionCheckbox.js.map +1 -1
  12. package/dist/devportal/hooks/usePermission.js +2 -2
  13. package/dist/devportal/hooks/usePermission.js.map +1 -1
  14. package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
  15. package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
  16. package/dist/devportal/pages/CreateApplicationPage.js +185 -104
  17. package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
  18. package/dist/devportal/pages/CreateWebhookPage.js +6 -5
  19. package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
  20. package/dist/devportal/pages/DashboardPage.js.map +1 -1
  21. package/dist/devportal/pages/OAuthCallbackPage.js +64 -0
  22. package/dist/devportal/pages/OAuthCallbackPage.js.map +1 -0
  23. package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
  24. package/dist/devportal/pages/OrganizationDetailPage.js.map +1 -1
  25. package/dist/devportal/pages/PublishPage.js +392 -187
  26. package/dist/devportal/pages/PublishPage.js.map +1 -1
  27. package/dist/devportal/pages/ResourcesPage.js.map +1 -1
  28. package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -1
  29. package/dist/devportal/pages/WebhookDetailPage.js.map +1 -1
  30. package/dist/devportal/pages/WebhooksPage.js +1 -1
  31. package/dist/devportal/pages/WebhooksPage.js.map +1 -1
  32. package/dist/devportal/types/index.js.map +1 -1
  33. package/dist/generated/global-operations.js +260 -174
  34. package/dist/generated/global-operations.js.map +1 -1
  35. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"WebhookDetailPage.js","names":[],"sources":["../../../src/devportal/pages/WebhookDetailPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n Clock,\n ExternalLink,\n Loader2,\n Pause,\n Play,\n Send,\n Trash2,\n Webhook,\n XCircle,\n AlertCircle,\n} from \"lucide-react\";\nimport { format } from \"date-fns\";\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { ConfirmDialog, Alert } from \"../components/ConfirmDialog\";\nimport { DataTable, DetailList } from \"../components/DataTable\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { SecretDisplay } from \"../components/SecretDisplay\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport {\n type GetWebhookDetailQuery,\n getErrorMessage,\n useDeleteWebhookMutation,\n useGetApplicationQuery,\n useGetWebhookDetailQuery,\n useTestWebhookMutation,\n useUpdateWebhookMutation,\n} from \"../hooks\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\n\nconst formatTimestamp = (value?: string | null) => {\n if (!value) return \"Never\";\n return format(new Date(value), \"MMM d, yyyy HH:mm:ss\");\n};\n\ntype WebhookDeliveryEntry =\n GetWebhookDetailQuery[\"webhookDeliveries\"][\"edges\"][number][\"node\"];\n\nexport const WebhookDetailPage: FC = () => {\n const { appId, webhookId } = useParams<{\n appId: string;\n webhookId: string;\n }>();\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { allowed: canUpdate } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_UPDATE,\n );\n const { allowed: canDelete } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_DELETE,\n );\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showTestDialog, setShowTestDialog] = useState(false);\n const [testMessage, setTestMessage] = useState<string | null>(null);\n const [testSuccess, setTestSuccess] = useState(false);\n\n const { data: appData } = useGetApplicationQuery({\n variables: { id: appId ?? \"\" },\n skip: !appId,\n fetchPolicy: \"cache-first\",\n });\n\n useEffect(() => {\n const name = appData?.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, appData?.application?.name]);\n\n const { data, loading, error, refetch } = useGetWebhookDetailQuery({\n variables: {\n id: webhookId || \"\",\n limit: 20,\n offset: 0,\n },\n skip: !webhookId,\n fetchPolicy: \"cache-and-network\",\n });\n const [updateWebhook, { loading: updateLoading }] =\n useUpdateWebhookMutation();\n const [deleteWebhook, { loading: deleteLoading }] =\n useDeleteWebhookMutation();\n const [testWebhook, { loading: testLoading }] = useTestWebhookMutation();\n\n const webhook = data?.webhook;\n\n useEffect(() => {\n const name = webhook?.name;\n if (!webhookId || !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(webhookId, name);\n return () => reg?.unregister(webhookId);\n }, [webhookId, webhook?.name]);\n\n const deliveries: WebhookDeliveryEntry[] =\n data?.webhookDeliveries?.edges?.map(\n (edge: GetWebhookDetailQuery[\"webhookDeliveries\"][\"edges\"][number]) =>\n edge.node,\n ) ?? [];\n const backAppId = appId || webhook?.applicationId;\n const successRate = useMemo(() => {\n if (!webhook) return 0;\n const total = (webhook.successCount ?? 0) + (webhook.failureCount ?? 0);\n if (total === 0) return 100;\n return Math.round(((webhook.successCount ?? 0) / total) * 100);\n }, [webhook]);\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleToggleActive = async () => {\n if (!webhook) return;\n\n await updateWebhook({\n variables: {\n id: webhook.id,\n input: {\n isActive: !webhook.isActive,\n },\n },\n });\n\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!webhook) return;\n void nativeImpact(\"medium\");\n\n try {\n await deleteWebhook({\n variables: {\n id: webhook.id,\n },\n });\n void nativeNotify(\"success\");\n handleNavigate(`/apps/${webhook.applicationId}/webhooks`);\n } catch {\n void nativeNotify(\"error\");\n }\n };\n\n const handleTest = async () => {\n if (!webhook) return;\n\n const result = await testWebhook({\n variables: {\n id: webhook.id,\n eventType: webhook.events[0] || \"application.updated\",\n },\n });\n\n const response = result.data?.testWebhook;\n if (!response) {\n setTestSuccess(false);\n setTestMessage(\"Test request did not return a response.\");\n return;\n }\n\n setTestSuccess(response.success);\n setTestMessage(\n response.success\n ? `Test delivery succeeded with HTTP ${response.statusCode ?? 200}.`\n : response.error || \"Webhook test failed.\",\n );\n\n await refetch();\n };\n\n if (loading && !webhook) {\n return (\n <PageLayout\n title={tr(\"devportal.webhook\", \"Webhook\")}\n description={tr(\n \"devportal.loadingWebhookDetails\",\n \"Loading webhook details\",\n )}\n backLink={backAppId ? `/apps/${backAppId}/webhooks` : \"/webhooks\"}\n backLabel={tr(\"devportal.backToWebhooks\", \"Back to Webhooks\")}\n >\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error || !webhook) {\n return (\n <PageLayout\n title={tr(\"devportal.webhook\", \"Webhook\")}\n description={tr(\"devportal.webhookDetails\", \"Webhook details\")}\n backLink={backAppId ? `/apps/${backAppId}/webhooks` : \"/webhooks\"}\n backLabel={tr(\"devportal.backToWebhooks\", \"Back to Webhooks\")}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\"devportal.unableToLoadWebhook\", \"Unable to load webhook\")}\n description={\n error\n ? getErrorMessage(error)\n : tr(\"devportal.webhookNotFound\", \"Webhook not found\")\n }\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={webhook.name}\n description={\n webhook.description || \"Webhook configuration and delivery history\"\n }\n purpose={tr(\n \"devportal.webhookDetailPurpose\",\n \"This endpoint receives the events you subscribed to, signed with your secret. Check the delivery history to confirm your server is responding with 2xx, send a test event, and pause it if your endpoint is down so deliveries are not lost to repeated failures.\",\n )}\n backLink={`/apps/${backAppId}/webhooks`}\n backLabel={tr(\"devportal.backToWebhooks\", \"Back to Webhooks\")}\n actions={\n <CTAOverflowMenu\n primary={{\n label: tr(\"devportal.test\", \"Test\"),\n icon: <Send size={16} />,\n onSelect: () => {\n setTestMessage(null);\n setShowTestDialog(true);\n },\n }}\n actions={[\n {\n label: webhook.isActive\n ? tr(\"devportal.pauseWebhook\", \"Pause\")\n : tr(\"devportal.resumeWebhook\", \"Resume\"),\n icon: webhook.isActive ? <Pause size={16} /> : <Play size={16} />,\n disabled: updateLoading || !canUpdate,\n onSelect: () => void handleToggleActive(),\n },\n {\n label: tr(\"devportal.deleteWebhook\", \"Delete\"),\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: !canDelete,\n onSelect: () => setShowDeleteDialog(true),\n },\n ]}\n />\n }\n >\n <EmphasisPanel>\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.successRate\", \"Success Rate\")}\n value={`${successRate}%`}\n icon={<CheckCircle size={20} />}\n />\n <StatCard\n title={tr(\"devportal.successfulDeliveries\", \"Successful Deliveries\")}\n value={(webhook.successCount ?? 0).toLocaleString()}\n icon={<CheckCircle size={20} />}\n />\n <StatCard\n title={tr(\"devportal.failedDeliveries\", \"Failed Deliveries\")}\n value={(webhook.failureCount ?? 0).toLocaleString()}\n icon={<XCircle size={20} />}\n />\n <StatCard\n title={tr(\"devportal.lastTriggered\", \"Last Triggered\")}\n value={\n webhook.lastTriggeredAt\n ? format(new Date(webhook.lastTriggeredAt), \"MMM d, HH:mm\")\n : tr(\"devportal.never\", \"Never\")\n }\n icon={<Clock size={20} />}\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n {/* Guided next steps (dismissable) */}\n <NextSteps\n storageKey={`devportal-webhook-${webhook.id}`}\n steps={[\n {\n id: \"test\",\n label: tr(\"devportal.sendTestEvent\", \"Send a test event\"),\n description: tr(\n \"devportal.webhookStepTest\",\n \"Confirm your endpoint receives and verifies the payload\",\n ),\n icon: <Send size={16} />,\n onClick: () => {\n setTestMessage(null);\n setShowTestDialog(true);\n },\n },\n {\n id: \"toggle\",\n label: webhook.isActive\n ? tr(\"devportal.pauseWebhook\", \"Pause deliveries\")\n : tr(\"devportal.resumeWebhook\", \"Resume deliveries\"),\n description: tr(\n \"devportal.webhookStepToggle\",\n \"Pause while your endpoint is down to avoid lost retries\",\n ),\n icon: webhook.isActive ? <Pause size={16} /> : <Play size={16} />,\n onClick: () => void handleToggleActive(),\n },\n ]}\n />\n\n <PageSection title={tr(\"devportal.configuration\", \"Configuration\")}>\n <DetailList\n items={[\n {\n label: tr(\"devportal.status\", \"Status\"),\n value: <StatusBadge status={webhook.status} type=\"webhook\" />,\n },\n {\n label: tr(\"devportal.endpointUrl\", \"Endpoint URL\"),\n value: (\n <span className=\"flex flex-wrap items-center gap-2 font-mono text-sm\">\n <span className=\"break-all\">{webhook.url}</span>\n <a\n href={webhook.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary\"\n >\n <ExternalLink size={14} />\n </a>\n </span>\n ),\n },\n {\n label: tr(\"devportal.signingSecret\", \"Signing Secret\"),\n value: (\n <SecretDisplay value={webhook.secret || \"******\"} label=\"\" />\n ),\n },\n {\n label: tr(\"devportal.subscribedEvents\", \"Subscribed Events\"),\n value: (\n <div className=\"flex flex-wrap gap-1\">\n {webhook.events.map((eventName: string) => (\n <code\n key={eventName}\n className=\"rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {eventName}\n </code>\n ))}\n </div>\n ),\n },\n {\n label: tr(\"devportal.retryPolicy\", \"Retry Policy\"),\n value: webhook.retryEnabled\n ? tr(\n \"devportal.retryPolicyEnabled\",\n `Up to ${webhook.maxRetries ?? 0} retries`,\n )\n : tr(\"devportal.retriesDisabled\", \"Retries disabled\"),\n },\n {\n label: tr(\"devportal.created\", \"Created\"),\n value: formatTimestamp(webhook.createdAt),\n },\n {\n label: tr(\"devportal.updated\", \"Updated\"),\n value: formatTimestamp(webhook.updatedAt),\n },\n ]}\n />\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.recentDeliveries\", \"Recent Deliveries\")}\n >\n {deliveries.length > 0 ? (\n <DataTable\n data={deliveries}\n keyField=\"id\"\n columns={[\n {\n key: \"status\",\n header: tr(\"devportal.status\", \"Status\"),\n render: (delivery) => (\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={delivery.status} type=\"delivery\" />\n </div>\n ),\n },\n {\n key: \"eventType\",\n header: tr(\"devportal.event\", \"Event\"),\n render: (delivery) => (\n <code className=\"text-xs\">{delivery.eventType}</code>\n ),\n },\n {\n key: \"responseStatus\",\n header: tr(\"devportal.response\", \"Response\"),\n render: (delivery) => delivery.responseStatus ?? \"-\",\n },\n {\n key: \"attemptNumber\",\n header: tr(\"devportal.attempts\", \"Attempts\"),\n render: (delivery) => delivery.attemptNumber,\n },\n {\n key: \"createdAt\",\n header: tr(\"devportal.created\", \"Created\"),\n render: (delivery) => formatTimestamp(delivery.createdAt),\n },\n ]}\n />\n ) : (\n <EmptyState\n illustration=\"empty-notifications\"\n icon={<Webhook size={24} />}\n title={tr(\"devportal.noDeliveriesYet\", \"No deliveries yet\")}\n description={tr(\n \"devportal.noDeliveriesYetDescription\",\n \"Deliveries will appear here after the first matching event is sent to this endpoint.\",\n )}\n />\n )}\n </PageSection>\n\n <ConfirmDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n onConfirm={() => void handleDelete()}\n loading={deleteLoading}\n title={tr(\"devportal.deleteWebhookTitle\", \"Delete webhook\")}\n description={tr(\n \"devportal.deleteWebhookDescription\",\n \"Deleting this webhook stops all future deliveries for this endpoint.\",\n )}\n confirmLabel={tr(\"devportal.deleteWebhookConfirm\", \"Delete webhook\")}\n variant=\"danger\"\n />\n\n <ConfirmDialog\n open={showTestDialog}\n onOpenChange={(open) => {\n setShowTestDialog(open);\n if (!open) {\n setTestMessage(null);\n }\n }}\n onConfirm={() => void handleTest()}\n loading={testLoading}\n title={tr(\"devportal.sendTestDelivery\", \"Send test delivery\")}\n description={tr(\n \"devportal.sendTestDeliveryDescription\",\n \"A signed test event will be sent to the configured endpoint through the public gateway.\",\n )}\n confirmLabel={tr(\"devportal.sendTestEvent\", \"Send test event\")}\n >\n {testMessage && (\n <Alert variant={testSuccess ? \"success\" : \"error\"}>\n {testMessage}\n </Alert>\n )}\n </ConfirmDialog>\n </PageLayout>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAM,KAAmB,MAClB,IACE,EAAO,IAAI,KAAK,EAAM,EAAE,uBAAuB,GADnC,SAOR,UAA8B;CACzC,IAAM,EAAE,UAAO,iBAAc,IAGzB,EACE,EAAE,aAAU,gBAAa,GAAc,EACvC,EAAE,SAAM,IAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAE9B,EAAE,SAAS,MAAc,EAC7B,EAAsB,eACvB,EACK,EAAE,SAAS,MAAc,EAC7B,EAAsB,eACvB,EACK,CAAC,GAAkB,KAAuB,EAAS,GAAM,EACzD,CAAC,IAAgB,KAAqB,EAAS,GAAM,EACrD,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,IAAa,KAAkB,EAAS,GAAM,EAE/C,EAAE,MAAM,MAAY,EAAuB;EAC/C,WAAW,EAAE,IAAI,KAAS,IAAI;EAC9B,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAEF,SAAgB;EACd,IAAM,IAAO,GAAS,aAAa;AACnC,MAAI,CAAC,KAAS,CAAC,EAAM;EACrB,IAAM,IACJ,OAMA;AAEF,SADA,GAAK,SAAS,GAAO,EAAK,QACb,GAAK,WAAW,EAAM;IAClC,CAAC,GAAO,GAAS,aAAa,KAAK,CAAC;CAEvC,IAAM,EAAE,SAAM,aAAS,UAAO,eAAY,EAAyB;EACjE,WAAW;GACT,IAAI,KAAa;GACjB,OAAO;GACP,QAAQ;GACT;EACD,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EACI,CAAC,IAAe,EAAE,SAAS,QAC/B,GAA0B,EACtB,CAAC,IAAe,EAAE,SAAS,QAC/B,GAA0B,EACtB,CAAC,IAAa,EAAE,SAAS,QAAiB,IAAwB,EAElE,IAAU,GAAM;AAEtB,SAAgB;EACd,IAAM,IAAO,GAAS;AACtB,MAAI,CAAC,KAAa,CAAC,EAAM;EACzB,IAAM,IACJ,OAMA;AAEF,SADA,GAAK,SAAS,GAAW,EAAK,QACjB,GAAK,WAAW,EAAU;IACtC,CAAC,GAAW,GAAS,KAAK,CAAC;CAE9B,IAAM,IACJ,GAAM,mBAAmB,OAAO,KAC7B,MACC,EAAK,KACR,IAAI,EAAE,EACH,IAAY,KAAS,GAAS,eAC9B,KAAc,QAAc;AAChC,MAAI,CAAC,EAAS,QAAO;EACrB,IAAM,KAAS,EAAQ,gBAAgB,MAAM,EAAQ,gBAAgB;AAErE,SADI,MAAU,IAAU,MACjB,KAAK,OAAQ,EAAQ,gBAAgB,KAAK,IAAS,IAAI;IAC7D,CAAC,EAAQ,CAAC,EAEP,MAAkB,MAAiB;AACvC,MAAW,GAAG,IAAW,IAAO;IAG5B,IAAqB,YAAY;AAChC,QAEL,MAAM,GAAc,EAClB,WAAW;GACT,IAAI,EAAQ;GACZ,OAAO,EACL,UAAU,CAAC,EAAQ,UACpB;GACF,EACF,CAAC,EAEF,MAAM,GAAS;IAGX,KAAe,YAAY;AAC1B,SACA;MAAa,SAAS;AAE3B,OAAI;AAOF,IANA,MAAM,GAAc,EAClB,WAAW,EACT,IAAI,EAAQ,IACb,EACF,CAAC,EACG,EAAa,UAAU,EAC5B,GAAe,SAAS,EAAQ,cAAc,WAAW;WACnD;AACD,MAAa,QAAQ;;;IAIxB,KAAa,YAAY;AAC7B,MAAI,CAAC,EAAS;EASd,IAAM,KAPS,MAAM,GAAY,EAC/B,WAAW;GACT,IAAI,EAAQ;GACZ,WAAW,EAAQ,OAAO,MAAM;GACjC,EACF,CAAC,EAEsB,MAAM;AAC9B,MAAI,CAAC,GAAU;AAEb,GADA,EAAe,GAAM,EACrB,EAAe,0CAA0C;AACzD;;AAUF,EAPA,EAAe,EAAS,QAAQ,EAChC,EACE,EAAS,UACL,qCAAqC,EAAS,cAAc,IAAI,KAChE,EAAS,SAAS,uBACvB,EAED,MAAM,GAAS;;AA0CjB,QAvCI,MAAW,CAAC,IAEZ,kBAAC,GAAD;EACE,OAAO,EAAG,qBAAqB,UAAU;EACzC,aAAa,EACX,mCACA,0BACD;EACD,UAAU,IAAY,SAAS,EAAU,aAAa;EACtD,WAAW,EAAG,4BAA4B,mBAAmB;YAE7D,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,IAAD,EAAS,WAAU,wBAAyB,CAAA;GACxC,CAAA;EACK,CAAA,GAIb,KAAS,CAAC,IAEV,kBAAC,GAAD;EACE,OAAO,EAAG,qBAAqB,UAAU;EACzC,aAAa,EAAG,4BAA4B,kBAAkB;EAC9D,UAAU,IAAY,SAAS,EAAU,aAAa;EACtD,WAAW,EAAG,4BAA4B,mBAAmB;YAE7D,kBAAC,GAAD;GACE,MAAM,kBAAC,IAAD;IAAa,MAAM;IAAI,WAAU;IAA2B,CAAA;GAClE,OAAO,EAAG,iCAAiC,yBAAyB;GACpE,aACE,IACI,EAAgB,EAAM,GACtB,EAAG,6BAA6B,oBAAoB;GAE1D,CAAA;EACS,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,EAAQ;EACf,aACE,EAAQ,eAAe;EAEzB,SAAS,EACP,kCACA,oQACD;EACD,UAAU,SAAS,EAAU;EAC7B,WAAW,EAAG,4BAA4B,mBAAmB;EAC7D,SACE,kBAAC,GAAD;GACE,SAAS;IACP,OAAO,EAAG,kBAAkB,OAAO;IACnC,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,gBAAgB;AAEd,KADA,EAAe,KAAK,EACpB,EAAkB,GAAK;;IAE1B;GACD,SAAS,CACP;IACE,OAAO,EAAQ,WACX,EAAG,0BAA0B,QAAQ,GACrC,EAAG,2BAA2B,SAAS;IAC3C,MAAM,EAAQ,WAAW,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,GAAG,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACjE,UAAU,MAAiB,CAAC;IAC5B,gBAAgB,KAAK,GAAoB;IAC1C,EACD;IACE,OAAO,EAAG,2BAA2B,SAAS;IAC9C,MAAM,kBAAC,IAAD,EAAQ,MAAM,IAAM,CAAA;IAC1B,QAAQ;IACR,UAAU,CAAC;IACX,gBAAgB,EAAoB,GAAK;IAC1C,CACF;GACD,CAAA;YAtCN;GAyCE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IAAc,SAAS;cAAvB;KACA,kBAAC,GAAD;MACE,OAAO,EAAG,yBAAyB,eAAe;MAClD,OAAO,GAAG,GAAY;MACtB,MAAM,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA;MAC/B,CAAA;KACF,kBAAC,GAAD;MACE,OAAO,EAAG,kCAAkC,wBAAwB;MACpE,QAAQ,EAAQ,gBAAgB,GAAG,gBAAgB;MACnD,MAAM,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA;MAC/B,CAAA;KACF,kBAAC,GAAD;MACE,OAAO,EAAG,8BAA8B,oBAAoB;MAC5D,QAAQ,EAAQ,gBAAgB,GAAG,gBAAgB;MACnD,MAAM,kBAAC,IAAD,EAAS,MAAM,IAAM,CAAA;MAC3B,CAAA;KACF,kBAAC,GAAD;MACE,OAAO,EAAG,2BAA2B,iBAAiB;MACtD,OACE,EAAQ,kBACJ,EAAO,IAAI,KAAK,EAAQ,gBAAgB,EAAE,eAAe,GACzD,EAAG,mBAAmB,QAAQ;MAEpC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;MACzB,CAAA;KACa;OACD,CAAA;GAGhB,kBAAC,GAAD;IACE,YAAY,qBAAqB,EAAQ;IACzC,OAAO,CACL;KACE,IAAI;KACJ,OAAO,EAAG,2BAA2B,oBAAoB;KACzD,aAAa,EACX,6BACA,0DACD;KACD,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;KACxB,eAAe;AAEb,MADA,EAAe,KAAK,EACpB,EAAkB,GAAK;;KAE1B,EACD;KACE,IAAI;KACJ,OAAO,EAAQ,WACX,EAAG,0BAA0B,mBAAmB,GAChD,EAAG,2BAA2B,oBAAoB;KACtD,aAAa,EACX,+BACA,0DACD;KACD,MAAM,EAAQ,WAAW,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,GAAG,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;KACjE,eAAe,KAAK,GAAoB;KACzC,CACF;IACD,CAAA;GAEF,kBAAC,GAAD;IAAa,OAAO,EAAG,2BAA2B,gBAAgB;cAChE,kBAAC,IAAD,EACE,OAAO;KACL;MACE,OAAO,EAAG,oBAAoB,SAAS;MACvC,OAAO,kBAAC,GAAD;OAAa,QAAQ,EAAQ;OAAQ,MAAK;OAAY,CAAA;MAC9D;KACD;MACE,OAAO,EAAG,yBAAyB,eAAe;MAClD,OACE,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAa,EAAQ;QAAW,CAAA,EAChD,kBAAC,KAAD;QACE,MAAM,EAAQ;QACd,QAAO;QACP,KAAI;QACJ,WAAU;kBAEV,kBAAC,IAAD,EAAc,MAAM,IAAM,CAAA;QACxB,CAAA,CACC;;MAEV;KACD;MACE,OAAO,EAAG,2BAA2B,iBAAiB;MACtD,OACE,kBAAC,IAAD;OAAe,OAAO,EAAQ,UAAU;OAAU,OAAM;OAAK,CAAA;MAEhE;KACD;MACE,OAAO,EAAG,8BAA8B,oBAAoB;MAC5D,OACE,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAQ,OAAO,KAAK,MACnB,kBAAC,QAAD;QAEE,WAAU;kBAET;QACI,EAJA,EAIA,CACP;OACE,CAAA;MAET;KACD;MACE,OAAO,EAAG,yBAAyB,eAAe;MAClD,OAAO,EAAQ,eACX,EACE,gCACA,SAAS,EAAQ,cAAc,EAAE,UAClC,GACD,EAAG,6BAA6B,mBAAmB;MACxD;KACD;MACE,OAAO,EAAG,qBAAqB,UAAU;MACzC,OAAO,EAAgB,EAAQ,UAAU;MAC1C;KACD;MACE,OAAO,EAAG,qBAAqB,UAAU;MACzC,OAAO,EAAgB,EAAQ,UAAU;MAC1C;KACF,EACD,CAAA;IACU,CAAA;GAEd,kBAAC,GAAD;IACE,OAAO,EAAG,8BAA8B,oBAAoB;cAE3D,EAAW,SAAS,IACnB,kBAAC,IAAD;KACE,MAAM;KACN,UAAS;KACT,SAAS;MACP;OACE,KAAK;OACL,QAAQ,EAAG,oBAAoB,SAAS;OACxC,SAAS,MACP,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SAAa,QAAQ,EAAS;SAAQ,MAAK;SAAa,CAAA;QACpD,CAAA;OAET;MACD;OACE,KAAK;OACL,QAAQ,EAAG,mBAAmB,QAAQ;OACtC,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBAAW,EAAS;QAAiB,CAAA;OAExD;MACD;OACE,KAAK;OACL,QAAQ,EAAG,sBAAsB,WAAW;OAC5C,SAAS,MAAa,EAAS,kBAAkB;OAClD;MACD;OACE,KAAK;OACL,QAAQ,EAAG,sBAAsB,WAAW;OAC5C,SAAS,MAAa,EAAS;OAChC;MACD;OACE,KAAK;OACL,QAAQ,EAAG,qBAAqB,UAAU;OAC1C,SAAS,MAAa,EAAgB,EAAS,UAAU;OAC1D;MACF;KACD,CAAA,GAEF,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,IAAD,EAAS,MAAM,IAAM,CAAA;KAC3B,OAAO,EAAG,6BAA6B,oBAAoB;KAC3D,aAAa,EACX,wCACA,uFACD;KACD,CAAA;IAEQ,CAAA;GAEd,kBAAC,GAAD;IACE,MAAM;IACN,cAAc;IACd,iBAAiB,KAAK,IAAc;IACpC,SAAS;IACT,OAAO,EAAG,gCAAgC,iBAAiB;IAC3D,aAAa,EACX,sCACA,uEACD;IACD,cAAc,EAAG,kCAAkC,iBAAiB;IACpE,SAAQ;IACR,CAAA;GAEF,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,MAAS;AAEtB,KADA,EAAkB,EAAK,EAClB,KACH,EAAe,KAAK;;IAGxB,iBAAiB,KAAK,IAAY;IAClC,SAAS;IACT,OAAO,EAAG,8BAA8B,qBAAqB;IAC7D,aAAa,EACX,yCACA,0FACD;IACD,cAAc,EAAG,2BAA2B,kBAAkB;cAE7D,KACC,kBAAC,GAAD;KAAO,SAAS,KAAc,YAAY;eACvC;KACK,CAAA;IAEI,CAAA;GACL"}
1
+ {"version":3,"file":"WebhookDetailPage.js","names":[],"sources":["../../../src/devportal/pages/WebhookDetailPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n Clock,\n ExternalLink,\n Loader2,\n Pause,\n Play,\n Send,\n Trash2,\n Webhook,\n XCircle,\n AlertCircle,\n} from \"lucide-react\";\nimport { format } from \"date-fns\";\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { ConfirmDialog, Alert } from \"../components/ConfirmDialog\";\nimport { DataTable, DetailList } from \"../components/DataTable\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { SecretDisplay } from \"../components/SecretDisplay\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport {\n type GetWebhookDetailQuery,\n getErrorMessage,\n useDeleteWebhookMutation,\n useGetApplicationQuery,\n useGetWebhookDetailQuery,\n useTestWebhookMutation,\n useUpdateWebhookMutation,\n} from \"../hooks\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\n\nconst formatTimestamp = (value?: string | null) => {\n if (!value) return \"Never\";\n return format(new Date(value), \"MMM d, yyyy HH:mm:ss\");\n};\n\ntype WebhookDeliveryEntry =\n GetWebhookDetailQuery[\"webhookDeliveries\"][\"edges\"][number][\"node\"];\n\nexport const WebhookDetailPage: FC = () => {\n const { appId, webhookId } = useParams<{\n appId: string;\n webhookId: string;\n }>();\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { allowed: canUpdate } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_UPDATE,\n );\n const { allowed: canDelete } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_DELETE,\n );\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showTestDialog, setShowTestDialog] = useState(false);\n const [testMessage, setTestMessage] = useState<string | null>(null);\n const [testSuccess, setTestSuccess] = useState(false);\n\n const { data: appData } = useGetApplicationQuery({\n variables: { id: appId ?? \"\" },\n skip: !appId,\n fetchPolicy: \"cache-first\",\n });\n\n useEffect(() => {\n const name = appData?.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, appData?.application?.name]);\n\n const { data, loading, error, refetch } = useGetWebhookDetailQuery({\n variables: {\n id: webhookId || \"\",\n limit: 20,\n offset: 0,\n },\n skip: !webhookId,\n fetchPolicy: \"cache-and-network\",\n });\n const [updateWebhook, { loading: updateLoading }] =\n useUpdateWebhookMutation();\n const [deleteWebhook, { loading: deleteLoading }] =\n useDeleteWebhookMutation();\n const [testWebhook, { loading: testLoading }] = useTestWebhookMutation();\n\n const webhook = data?.webhook;\n\n useEffect(() => {\n const name = webhook?.name;\n if (!webhookId || !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(webhookId, name);\n return () => reg?.unregister(webhookId);\n }, [webhookId, webhook?.name]);\n\n const deliveries: WebhookDeliveryEntry[] =\n data?.webhookDeliveries?.edges?.map(\n (edge: GetWebhookDetailQuery[\"webhookDeliveries\"][\"edges\"][number]) =>\n edge.node,\n ) ?? [];\n const backAppId = appId || webhook?.applicationId;\n const successRate = useMemo(() => {\n if (!webhook) return 0;\n const total = (webhook.successCount ?? 0) + (webhook.failureCount ?? 0);\n if (total === 0) return 100;\n return Math.round(((webhook.successCount ?? 0) / total) * 100);\n }, [webhook]);\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleToggleActive = async () => {\n if (!webhook) return;\n\n await updateWebhook({\n variables: {\n id: webhook.id,\n input: {\n isActive: !webhook.isActive,\n },\n },\n });\n\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!webhook) return;\n void nativeImpact(\"medium\");\n\n try {\n await deleteWebhook({\n variables: {\n id: webhook.id,\n },\n });\n void nativeNotify(\"success\");\n handleNavigate(`/apps/${webhook.applicationId}/webhooks`);\n } catch {\n void nativeNotify(\"error\");\n }\n };\n\n const handleTest = async () => {\n if (!webhook) return;\n\n const result = await testWebhook({\n variables: {\n id: webhook.id,\n eventType: webhook.events[0] || \"application.updated\",\n },\n });\n\n const response = result.data?.testWebhook;\n if (!response) {\n setTestSuccess(false);\n setTestMessage(\"Test request did not return a response.\");\n return;\n }\n\n setTestSuccess(response.success);\n setTestMessage(\n response.success\n ? `Test delivery succeeded with HTTP ${response.statusCode ?? 200}.`\n : response.error || \"Webhook test failed.\",\n );\n\n await refetch();\n };\n\n if (loading && !webhook) {\n return (\n <PageLayout\n title={tr(\"devportal.webhook\", \"Webhook\")}\n description={tr(\n \"devportal.loadingWebhookDetails\",\n \"Loading webhook details\",\n )}\n backLink={backAppId ? `/apps/${backAppId}/webhooks` : \"/webhooks\"}\n backLabel={tr(\"devportal.backToWebhooks\", \"Back to Webhooks\")}\n >\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error || !webhook) {\n return (\n <PageLayout\n title={tr(\"devportal.webhook\", \"Webhook\")}\n description={tr(\"devportal.webhookDetails\", \"Webhook details\")}\n backLink={backAppId ? `/apps/${backAppId}/webhooks` : \"/webhooks\"}\n backLabel={tr(\"devportal.backToWebhooks\", \"Back to Webhooks\")}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\"devportal.unableToLoadWebhook\", \"Unable to load webhook\")}\n description={\n error\n ? getErrorMessage(error)\n : tr(\"devportal.webhookNotFound\", \"Webhook not found\")\n }\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={webhook.name}\n description={\n webhook.description || \"Webhook configuration and delivery history\"\n }\n purpose={tr(\n \"devportal.webhookDetailPurpose\",\n \"This endpoint receives the events you subscribed to, signed with your secret. Check the delivery history to confirm your server is responding with 2xx, send a test event, and pause it if your endpoint is down so deliveries are not lost to repeated failures.\",\n )}\n backLink={`/apps/${backAppId}/webhooks`}\n backLabel={tr(\"devportal.backToWebhooks\", \"Back to Webhooks\")}\n actions={\n <CTAOverflowMenu\n primary={{\n label: tr(\"devportal.test\", \"Test\"),\n icon: <Send size={16} />,\n onSelect: () => {\n setTestMessage(null);\n setShowTestDialog(true);\n },\n }}\n actions={[\n {\n label: webhook.isActive\n ? tr(\"devportal.pauseWebhook\", \"Pause\")\n : tr(\"devportal.resumeWebhook\", \"Resume\"),\n icon: webhook.isActive ? <Pause size={16} /> : <Play size={16} />,\n disabled: updateLoading || !canUpdate,\n onSelect: () => void handleToggleActive(),\n },\n {\n label: tr(\"devportal.deleteWebhook\", \"Delete\"),\n icon: <Trash2 size={16} />,\n intent: \"destructive\",\n disabled: !canDelete,\n onSelect: () => setShowDeleteDialog(true),\n },\n ]}\n />\n }\n >\n <EmphasisPanel>\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.successRate\", \"Success Rate\")}\n value={`${successRate}%`}\n icon={<CheckCircle size={20} />}\n />\n <StatCard\n title={tr(\n \"devportal.successfulDeliveries\",\n \"Successful Deliveries\",\n )}\n value={(webhook.successCount ?? 0).toLocaleString()}\n icon={<CheckCircle size={20} />}\n />\n <StatCard\n title={tr(\"devportal.failedDeliveries\", \"Failed Deliveries\")}\n value={(webhook.failureCount ?? 0).toLocaleString()}\n icon={<XCircle size={20} />}\n />\n <StatCard\n title={tr(\"devportal.lastTriggered\", \"Last Triggered\")}\n value={\n webhook.lastTriggeredAt\n ? format(new Date(webhook.lastTriggeredAt), \"MMM d, HH:mm\")\n : tr(\"devportal.never\", \"Never\")\n }\n icon={<Clock size={20} />}\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n {/* Guided next steps (dismissable) */}\n <NextSteps\n storageKey={`devportal-webhook-${webhook.id}`}\n steps={[\n {\n id: \"test\",\n label: tr(\"devportal.sendTestEvent\", \"Send a test event\"),\n description: tr(\n \"devportal.webhookStepTest\",\n \"Confirm your endpoint receives and verifies the payload\",\n ),\n icon: <Send size={16} />,\n onClick: () => {\n setTestMessage(null);\n setShowTestDialog(true);\n },\n },\n {\n id: \"toggle\",\n label: webhook.isActive\n ? tr(\"devportal.pauseWebhook\", \"Pause deliveries\")\n : tr(\"devportal.resumeWebhook\", \"Resume deliveries\"),\n description: tr(\n \"devportal.webhookStepToggle\",\n \"Pause while your endpoint is down to avoid lost retries\",\n ),\n icon: webhook.isActive ? <Pause size={16} /> : <Play size={16} />,\n onClick: () => void handleToggleActive(),\n },\n ]}\n />\n\n <PageSection title={tr(\"devportal.configuration\", \"Configuration\")}>\n <DetailList\n items={[\n {\n label: tr(\"devportal.status\", \"Status\"),\n value: <StatusBadge status={webhook.status} type=\"webhook\" />,\n },\n {\n label: tr(\"devportal.endpointUrl\", \"Endpoint URL\"),\n value: (\n <span className=\"flex flex-wrap items-center gap-2 font-mono text-sm\">\n <span className=\"break-all\">{webhook.url}</span>\n <a\n href={webhook.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary\"\n >\n <ExternalLink size={14} />\n </a>\n </span>\n ),\n },\n {\n label: tr(\"devportal.signingSecret\", \"Signing Secret\"),\n value: (\n <SecretDisplay value={webhook.secret || \"******\"} label=\"\" />\n ),\n },\n {\n label: tr(\"devportal.subscribedEvents\", \"Subscribed Events\"),\n value: (\n <div className=\"flex flex-wrap gap-1\">\n {webhook.events.map((eventName: string) => (\n <code\n key={eventName}\n className=\"rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {eventName}\n </code>\n ))}\n </div>\n ),\n },\n {\n label: tr(\"devportal.retryPolicy\", \"Retry Policy\"),\n value: webhook.retryEnabled\n ? tr(\n \"devportal.retryPolicyEnabled\",\n `Up to ${webhook.maxRetries ?? 0} retries`,\n )\n : tr(\"devportal.retriesDisabled\", \"Retries disabled\"),\n },\n {\n label: tr(\"devportal.created\", \"Created\"),\n value: formatTimestamp(webhook.createdAt),\n },\n {\n label: tr(\"devportal.updated\", \"Updated\"),\n value: formatTimestamp(webhook.updatedAt),\n },\n ]}\n />\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.recentDeliveries\", \"Recent Deliveries\")}\n >\n {deliveries.length > 0 ? (\n <DataTable\n data={deliveries}\n keyField=\"id\"\n columns={[\n {\n key: \"status\",\n header: tr(\"devportal.status\", \"Status\"),\n render: (delivery) => (\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={delivery.status} type=\"delivery\" />\n </div>\n ),\n },\n {\n key: \"eventType\",\n header: tr(\"devportal.event\", \"Event\"),\n render: (delivery) => (\n <code className=\"text-xs\">{delivery.eventType}</code>\n ),\n },\n {\n key: \"responseStatus\",\n header: tr(\"devportal.response\", \"Response\"),\n render: (delivery) => delivery.responseStatus ?? \"-\",\n },\n {\n key: \"attemptNumber\",\n header: tr(\"devportal.attempts\", \"Attempts\"),\n render: (delivery) => delivery.attemptNumber,\n },\n {\n key: \"createdAt\",\n header: tr(\"devportal.created\", \"Created\"),\n render: (delivery) => formatTimestamp(delivery.createdAt),\n },\n ]}\n />\n ) : (\n <EmptyState\n illustration=\"empty-notifications\"\n icon={<Webhook size={24} />}\n title={tr(\"devportal.noDeliveriesYet\", \"No deliveries yet\")}\n description={tr(\n \"devportal.noDeliveriesYetDescription\",\n \"Deliveries will appear here after the first matching event is sent to this endpoint.\",\n )}\n />\n )}\n </PageSection>\n\n <ConfirmDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n onConfirm={() => void handleDelete()}\n loading={deleteLoading}\n title={tr(\"devportal.deleteWebhookTitle\", \"Delete webhook\")}\n description={tr(\n \"devportal.deleteWebhookDescription\",\n \"Deleting this webhook stops all future deliveries for this endpoint.\",\n )}\n confirmLabel={tr(\"devportal.deleteWebhookConfirm\", \"Delete webhook\")}\n variant=\"danger\"\n />\n\n <ConfirmDialog\n open={showTestDialog}\n onOpenChange={(open) => {\n setShowTestDialog(open);\n if (!open) {\n setTestMessage(null);\n }\n }}\n onConfirm={() => void handleTest()}\n loading={testLoading}\n title={tr(\"devportal.sendTestDelivery\", \"Send test delivery\")}\n description={tr(\n \"devportal.sendTestDeliveryDescription\",\n \"A signed test event will be sent to the configured endpoint through the public gateway.\",\n )}\n confirmLabel={tr(\"devportal.sendTestEvent\", \"Send test event\")}\n >\n {testMessage && (\n <Alert variant={testSuccess ? \"success\" : \"error\"}>\n {testMessage}\n </Alert>\n )}\n </ConfirmDialog>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -26,7 +26,7 @@ function j(e) {
26
26
  name: e.name,
27
27
  description: e.description ?? void 0,
28
28
  url: e.url,
29
- secret: e.secret ?? void 0,
29
+ secret: void 0,
30
30
  events: e.events ?? [],
31
31
  isActive: e.isActive,
32
32
  status: e.status,
@@ -1 +1 @@
1
- {"version":3,"file":"WebhooksPage.js","names":[],"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-dom\";\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: webhook.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 permission={DEVPORTAL_PERMISSIONS.WEBHOOK_READ}>\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 permission={DEVPORTAL_PERMISSIONS.WEBHOOK_READ}>\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\"\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":[],"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-dom\";\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 permission={DEVPORTAL_PERMISSIONS.WEBHOOK_READ}>\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 permission={DEVPORTAL_PERMISSIONS.WEBHOOK_READ}>\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\"\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 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/devportal/types/index.ts"],"sourcesContent":["/**\n * Developer Portal Types\n *\n * Core type definitions for the Developer Portal microfrontend\n */\n\nimport type { ReactNode } from \"react\";\n\n// =============================================================================\n// NAVIGATION TYPES\n// =============================================================================\n\n/**\n * Navigation item for shell integration\n */\nexport interface NavItem {\n id: string;\n label: string;\n path: string;\n icon?: ReactNode;\n badge?: number;\n order?: number;\n group?: string;\n visible?: boolean;\n children?: NavItem[];\n}\n\n/**\n * Footer item for shell integration\n */\nexport interface FooterItem {\n id: string;\n label: string;\n type: \"status\" | \"action\";\n order?: number;\n section: \"left\" | \"center\" | \"right\";\n}\n\n// =============================================================================\n// ROOT PROPS\n// =============================================================================\n\n/**\n * Props for the DevportalRoot component\n */\nexport interface DevportalRootProps {\n /**\n * Base path for the devportal microfrontend\n */\n basePath?: string;\n\n /**\n * Tenant ID for scoping operations\n */\n tenantId?: string;\n\n /**\n * Workspace ID (optional, for workspace-scoped features)\n */\n workspaceId?: string;\n\n /**\n * API Gateway URL for GraphQL requests (global gateway)\n */\n apiGatewayUrl?: string;\n\n /**\n * Workspace gateway URL for workspace-layer operations (e.g. wspace-files-svc uploads)\n * e.g. \"https://graphqlworkspaces.burdenoff.com\" or \"http://localhost:4003\"\n */\n workspacesGatewayUrl?: string;\n\n /**\n * Authentication token for API requests\n */\n authToken?: string;\n\n /**\n * Workspace authentication token for workspace-gateway requests (X-Workspace-Authorization).\n * May be a cached/stale value — use getWorkspaceToken() for upload flows that need a fresh token.\n */\n workspaceToken?: string;\n\n /**\n * Async getter that always returns a valid (non-expired) workspace token.\n * Call this instead of reading workspaceToken directly for workspace-layer mutations.\n */\n getWorkspaceToken?: () => Promise<string | null>;\n\n /**\n * Navigation function provided by the shell\n */\n navigate?: (path: string) => void;\n\n /**\n * Current user information\n */\n currentUser?: {\n id: string;\n email: string;\n firstName?: string;\n lastName?: string;\n avatar?: string;\n };\n\n /**\n * Optional function to register navigation items with the shell\n */\n registerNavItems?: (items: NavItem | NavItem[]) => () => void;\n\n /**\n * Optional function to register footer items with the shell\n */\n registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;\n\n /**\n * Optional callback when an application is selected\n */\n onApplicationSelect?: (app: Application) => void;\n\n /**\n * Optional callback when an organization is selected\n */\n onOrganizationSelect?: (org: DeveloperOrganization) => void;\n}\n\n// =============================================================================\n// APPLICATION TYPES\n// =============================================================================\n\nexport type AppType =\n | \"WEB_APP\"\n | \"MOBILE_APP\"\n | \"DESKTOP_APP\"\n | \"CLI_TOOL\"\n | \"API_INTEGRATION\"\n | \"WORKFLOW\"\n | \"AUTOMATION\"\n | \"WIDGET\"\n | \"BOT\"\n | \"SERVICE\"\n | \"INTEGRATION\"\n | \"ANALYST\"\n | \"ASSISTANT\"\n | \"SPECIALIST\"\n | \"NODE\"\n | \"VIBEMODULE\"\n | \"CONSOLE\"\n | \"DASHBOARD\"\n | \"PARSER\"\n | \"DATASINK\";\n\nexport type AppStatus =\n | \"DRAFT\"\n | \"SUBMITTED\"\n | \"IN_REVIEW\"\n | \"APPROVED\"\n | \"PUBLISHED\"\n | \"REJECTED\"\n | \"SUSPENDED\"\n | \"DEPRECATED\"\n | \"ACTIVE\"\n | \"INACTIVE\"\n | \"REVOKED\";\n\nexport type AppVisibility = \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\" | \"INTERNAL\";\n\nexport type AppSubmissionReviewStatus =\n | \"PENDING\"\n | \"IN_PROGRESS\"\n | \"APPROVED\"\n | \"REJECTED\"\n | \"CHANGES_REQUESTED\";\n\nexport interface Application {\n id: string;\n name: string;\n slug: string;\n shortDescription?: string;\n description?: string;\n appType: AppType;\n status: AppStatus;\n visibility: AppVisibility;\n tenantId: string;\n developerId: string;\n developerOrganizationId?: string;\n developerOrganization?: DeveloperOrganization;\n category?: string;\n tags: string[];\n keywords: string[];\n iconUrl?: string;\n bannerUrl?: string;\n videoUrl?: string;\n screenshots?: Record<string, unknown>;\n websiteUrl?: string;\n documentationUrl?: string;\n supportUrl?: string;\n privacyPolicyUrl?: string;\n termsOfServiceUrl?: string;\n githubUrl?: string;\n pricingType?: string;\n pricingDetails?: Record<string, unknown>;\n installCount: number;\n activeInstallCount: number;\n averageRating?: number;\n reviewCount: number;\n submittedAt?: string;\n publishedAt?: string;\n lastReviewedAt?: string;\n reviewStatus: AppSubmissionReviewStatus;\n reviewNotes?: string;\n reviewedBy?: string;\n isFeatured: boolean;\n isVerified: boolean;\n requiresApproval: boolean;\n manifest?: Record<string, unknown>;\n config?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n appId?: string;\n createdAt: string;\n updatedAt: string;\n versions?: ApplicationVersion[];\n oauthClients?: OAuthClient[];\n apiKeys?: ApiKey[];\n webhooks?: Webhook[];\n scopes?: ApplicationScope[];\n}\n\nexport interface ApplicationVersion {\n id: string;\n applicationId: string;\n version: string;\n releaseNotes?: string;\n changelog?: string;\n status: string;\n isLatest: boolean;\n isStable: boolean;\n packageUrl?: string;\n packageSize?: string;\n packageHash?: string;\n manifest?: Record<string, unknown>;\n minPlatformVersion?: string;\n maxPlatformVersion?: string;\n requiredScopes: string[];\n publishedAt?: string;\n deprecatedAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ApplicationScope {\n id: string;\n applicationId: string;\n scope: string;\n description?: string;\n isRequired: boolean;\n createdAt: string;\n}\n\n// =============================================================================\n// OAUTH TYPES\n// =============================================================================\n\nexport type OAuthClientType = \"PUBLIC\" | \"CONFIDENTIAL\";\n\nexport type OAuthGrantType =\n | \"AUTHORIZATION_CODE\"\n | \"REFRESH_TOKEN\"\n | \"CLIENT_CREDENTIALS\"\n | \"IMPLICIT\"\n | \"PASSWORD\";\n\nexport interface OAuthClient {\n id: string;\n applicationId: string;\n name: string;\n clientId: string;\n clientSecret: string;\n clientType: OAuthClientType;\n redirectUris: string[];\n allowedGrantTypes: OAuthGrantType[];\n allowedScopes: string[];\n accessTokenLifetime: number;\n refreshTokenLifetime: number;\n isActive: boolean;\n requirePKCE: boolean;\n requireConsent: boolean;\n metadata?: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface OAuthToken {\n id: string;\n clientId: string;\n userId: string;\n tenantId: string;\n workspaceId?: string;\n accessToken: string;\n refreshToken?: string;\n tokenType: string;\n scopes: string[];\n expiresAt: string;\n refreshExpiresAt?: string;\n isRevoked: boolean;\n revokedAt?: string;\n createdAt: string;\n lastUsedAt?: string;\n}\n\n// =============================================================================\n// API KEY TYPES\n// =============================================================================\n\nexport type ApiKeyType = \"PUBLISHABLE\" | \"SECRET\" | \"RESTRICTED\";\n\nexport interface ApiKey {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n keyId: string;\n keyHash?: string; // Hash is not always returned by API for security\n keyPrefix: string;\n keyType: ApiKeyType;\n scopes: string[];\n permissions?: Record<string, unknown>;\n rateLimit?: number;\n quotaLimit?: number;\n quotaUsed: number;\n quotaResetAt?: string;\n allowedIps: string[];\n allowedDomains: string[];\n environment?: string;\n isActive: boolean;\n isRevoked: boolean;\n createdAt: string;\n lastUsedAt?: string;\n expiresAt?: string;\n revokedAt?: string;\n}\n\nexport interface ApiKeyPayload {\n apiKey: ApiKey;\n plainTextKey: string;\n}\n\n// =============================================================================\n// WEBHOOK TYPES\n// =============================================================================\n\nexport type WebhookStatus = \"ACTIVE\" | \"PAUSED\" | \"FAILED\" | \"DISABLED\";\n\nexport type WebhookDeliveryStatus =\n | \"PENDING\"\n | \"DELIVERING\"\n | \"DELIVERED\"\n | \"FAILED\"\n | \"RETRYING\";\n\nexport interface Webhook {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n url: string;\n secret: string;\n events: string[];\n filters?: Record<string, unknown>;\n retryEnabled: boolean;\n maxRetries: number;\n retryDelay: number;\n status: WebhookStatus;\n isActive: boolean;\n successCount: number;\n failureCount: number;\n lastTriggeredAt?: string;\n lastSuccessAt?: string;\n lastFailureAt?: string;\n lastFailureReason?: string;\n createdAt: string;\n updatedAt: string;\n deliveries?: WebhookDelivery[];\n}\n\nexport interface WebhookDelivery {\n id: string;\n webhookId: string;\n eventType: string;\n eventId: string;\n payload: Record<string, unknown>;\n attemptNumber: number;\n status: WebhookDeliveryStatus;\n requestHeaders?: Record<string, unknown>;\n requestBody?: Record<string, unknown>;\n responseStatus?: number;\n responseHeaders?: Record<string, unknown>;\n responseBody?: string;\n deliveredAt?: string;\n failedAt?: string;\n nextRetryAt?: string;\n errorMessage?: string;\n errorCode?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface WebhookTestResult {\n success: boolean;\n statusCode?: number;\n response?: string;\n error?: string;\n}\n\n// =============================================================================\n// DEVELOPER ORGANIZATION TYPES\n// =============================================================================\n\nexport type DeveloperOrgRole = \"OWNER\" | \"ADMIN\" | \"DEVELOPER\" | \"VIEWER\";\n\nexport type DeveloperOrgTier =\n | \"FREE\"\n | \"DEVELOPER\"\n | \"PROFESSIONAL\"\n | \"ENTERPRISE\";\n\nexport type PartnerTier = \"REGISTERED\" | \"SELECT\" | \"PREMIER\";\n\nexport type InvitationStatus =\n | \"PENDING\"\n | \"ACCEPTED\"\n | \"REJECTED\"\n | \"EXPIRED\"\n | \"CANCELLED\";\n\nexport interface DeveloperOrganization {\n id: string;\n name: string;\n slug: string;\n description?: string;\n logoUrl?: string;\n bannerUrl?: string;\n websiteUrl?: string;\n contactEmail?: string;\n supportEmail?: string;\n tier: DeveloperOrgTier;\n partnerTier?: PartnerTier;\n partnerSince?: string;\n isVerified: boolean;\n verifiedAt?: string;\n verificationMethod?: string;\n isCertified: boolean;\n certifiedAt?: string;\n certificationExpiry?: string;\n totalApps: number;\n publishedApps: number;\n totalInstalls: number;\n activeInstalls: number;\n totalApiCalls: string;\n averageRating?: number;\n totalRevenue: number;\n totalPlatformFees: number;\n totalPayouts: number;\n pendingBalance: number;\n lifetimeRevenue: number;\n platformFeeRate: number;\n apiCallsLimit: number;\n storageLimit: string;\n apiCallsUsed: string;\n storageUsed: string;\n quotaResetAt?: string;\n taxId?: string;\n taxCountry?: string;\n taxFormType?: string;\n taxFormVerified: boolean;\n legalEntityName?: string;\n legalEntityType?: string;\n legalAddress?: Record<string, unknown>;\n settings?: Record<string, unknown>;\n billingAccountId?: string;\n billingLinkedAt?: string;\n createdAt: string;\n updatedAt: string;\n members?: DeveloperOrgMember[];\n invitations?: DeveloperOrgInvitation[];\n applications?: Application[];\n}\n\nexport interface DeveloperOrgMember {\n id: string;\n organizationId: string;\n userId: string;\n email?: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy?: string;\n invitationId?: string;\n isActive: boolean;\n isSuspended: boolean;\n suspendedAt?: string;\n suspendedReason?: string;\n joinedAt: string;\n lastActiveAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DeveloperOrgInvitation {\n id: string;\n organizationId: string;\n invitedUserId?: string;\n invitedEmail: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy: string;\n message?: string;\n token: string;\n status: InvitationStatus;\n expiresAt: string;\n respondedAt?: string;\n memberId?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// ANALYTICS TYPES (MOCKED)\n// =============================================================================\n\nexport interface ApplicationAnalytics {\n id: string;\n applicationId: string;\n date: string;\n period: string;\n activeUsers: number;\n newUsers: number;\n apiCalls: number;\n errors: number;\n avgResponseTime?: number;\n sessions: number;\n avgSessionDuration?: number;\n createdAt: string;\n}\n\nexport interface DeveloperOrgRevenueAnalytics {\n id: string;\n organizationId: string;\n period: string;\n periodStart: string;\n periodEnd: string;\n grossRevenue: number;\n platformFees: number;\n netRevenue: number;\n salesCount: number;\n refundsCount: number;\n refundsAmount: number;\n newSubscribers: number;\n churnedSubscribers: number;\n activeSubscribers: number;\n avgRevenuePerUser: number;\n topApps?: Array<{ appId: string; revenue: number }>;\n createdAt: string;\n}\n\n// =============================================================================\n// MONETIZATION TYPES (MOCKED)\n// =============================================================================\n\nexport type DevPortalPricingModel =\n | \"FREE\"\n | \"FREEMIUM\"\n | \"SUBSCRIPTION\"\n | \"ONE_TIME\"\n | \"USAGE_BASED\"\n | \"ENTERPRISE\";\n\nexport type SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\nexport type PayoutStatus =\n | \"PENDING\"\n | \"PROCESSING\"\n | \"COMPLETED\"\n | \"FAILED\"\n | \"CANCELLED\";\n\nexport interface DeveloperOrgSale {\n id: string;\n organizationId: string;\n applicationId: string;\n buyerUserId: string;\n buyerTenantId?: string;\n installationScope: string;\n pricingModel: DevPortalPricingModel;\n planName?: string;\n grossAmount: number;\n platformFeeRate: number;\n platformFee: number;\n netAmount: number;\n currency: string;\n taxAmount?: number;\n discountAmount?: number;\n status: SaleStatus;\n createdAt: string;\n}\n\nexport interface DeveloperOrgPayout {\n id: string;\n organizationId: string;\n periodStart: string;\n periodEnd: string;\n grossAmount: number;\n platformFees: number;\n netAmount: number;\n currency: string;\n status: PayoutStatus;\n processedAt?: string;\n failedAt?: string;\n failureReason?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// APP INSTALLATION TYPES\n// =============================================================================\n\nexport type AppInstallationStatus =\n | \"ACTIVE\"\n | \"SUSPENDED\"\n | \"TRIAL\"\n | \"EXPIRED\"\n | \"UNINSTALLED\";\n\nexport interface AppInstallation {\n id: string;\n applicationId: string;\n userId: string;\n tenantId?: string;\n workspaceId?: string;\n organizationId?: string;\n scope: string;\n installedVersion: string;\n status: AppInstallationStatus;\n grantedScopes: string[];\n settings?: Record<string, unknown>;\n installedAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// REVIEW TYPES\n// =============================================================================\n\nexport type ReviewStatus =\n | \"PENDING\"\n | \"PUBLISHED\"\n | \"HIDDEN\"\n | \"FLAGGED\"\n | \"REMOVED\";\n\nexport interface AppReview {\n id: string;\n applicationId: string;\n userId: string;\n rating: number;\n title?: string;\n body?: string;\n status: ReviewStatus;\n isFeatured: boolean;\n helpfulCount: number;\n notHelpfulCount: number;\n developerResponse?: string;\n developerResponseAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// DELEGATION TYPES\n// =============================================================================\n\nexport type DelegationStatus = \"ACTIVE\" | \"REVOKED\" | \"EXPIRED\";\n\nexport interface AppUserDelegation {\n id: string;\n appId: string;\n app?: {\n id: string;\n name: string;\n description?: string;\n type: string;\n status: string;\n };\n userId: string;\n scopes: string[];\n workspaceIds: string[];\n status: DelegationStatus;\n grantedAt: string;\n grantedBy: string;\n expiresAt?: string;\n revokedAt?: string;\n revokedBy?: string;\n}\n\nexport interface GrantDelegationInput {\n appId: string;\n userId?: string;\n workspaceIds: string[];\n scopes: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUTH APP TYPES (from auth-svc layer)\n// =============================================================================\n\nexport type AuthAppType =\n | \"ANALYST\"\n | \"API_INTEGRATION\"\n | \"ASSISTANT\"\n | \"AUTOMATION\"\n | \"BOT\"\n | \"CLI_TOOL\"\n | \"DESKTOP_APP\"\n | \"INTEGRATION\"\n | \"MOBILE_APP\"\n | \"SERVICE\"\n | \"SPECIALIST\"\n | \"WEB_APP\"\n | \"WIDGET\"\n | \"WORKFLOW\";\nexport type AuthAppStatus = \"ACTIVE\" | \"INACTIVE\" | \"REVOKED\";\n\nexport interface AuthApp {\n id: string;\n name: string;\n description?: string;\n clientId: string;\n type: AuthAppType;\n status: AuthAppStatus;\n scopes: string[];\n redirectUris: string[];\n ownerUserId: string;\n tenantId: string;\n delegations?: AppUserDelegation[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface AuthAppCreateResponse {\n app: AuthApp;\n clientSecret: string;\n}\n\nexport interface CreateAuthAppInput {\n name: string;\n description?: string;\n type: AuthAppType;\n scopes: string[];\n redirectUris?: string[];\n tenantId?: string;\n}\n\nexport interface UpdateAuthAppInput {\n name?: string;\n description?: string;\n scopes?: string[];\n redirectUris?: string[];\n}\n\n// =============================================================================\n// AUTH API KEY TYPES (from auth-svc layer)\n// =============================================================================\n\nexport interface AuthApiKey {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string;\n createdAt: string;\n expiresAt?: string;\n lastUsedAt?: string;\n}\n\nexport interface AuthApiKeyResult {\n apiKey: string;\n apiSecret: string;\n keyId: string;\n}\n\nexport interface AuthApiKeyRotateResult {\n apiSecret: string;\n}\n\nexport interface GenerateAuthApiKeyInput {\n appId: string;\n name: string;\n scope: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUDIT LOG TYPES\n// =============================================================================\n\nexport interface AuditLog {\n id: string;\n actorId: string;\n actorType: string;\n tenantId: string;\n action: string;\n resource: string;\n resourceId?: string;\n changes?: Record<string, unknown>;\n ipAddress?: string;\n userAgent?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// VIEW TYPES\n// =============================================================================\n\nexport type DevportalView =\n | \"dashboard\"\n | \"organizations\"\n | \"apps\"\n | \"oauth\"\n | \"api-keys\"\n | \"webhooks\"\n | \"delegations\"\n | \"analytics\"\n | \"revenue\"\n | \"docs\"\n | \"profile\"\n | \"settings\";\n\n// =============================================================================\n// FILTER TYPES\n// =============================================================================\n\nexport interface ApplicationFilter {\n search?: string;\n status?: AppStatus;\n visibility?: AppVisibility;\n appType?: AppType;\n organizationId?: string;\n isFeatured?: boolean;\n}\n\nexport interface WebhookFilter {\n status?: WebhookStatus;\n event?: string;\n}\n\n// =============================================================================\n// WEBHOOK EVENT TYPES\n// =============================================================================\n\nexport const WEBHOOK_EVENTS = [\n // Application events\n \"application.created\",\n \"application.updated\",\n \"application.deleted\",\n \"application.published\",\n \"application.unpublished\",\n \"application.suspended\",\n // Installation events\n \"installation.created\",\n \"installation.updated\",\n \"installation.deleted\",\n // User events\n \"user.created\",\n \"user.updated\",\n \"user.deleted\",\n // Workspace events\n \"workspace.created\",\n \"workspace.updated\",\n \"workspace.deleted\",\n // Project events\n \"project.created\",\n \"project.updated\",\n \"project.deleted\",\n // Task events\n \"task.created\",\n \"task.updated\",\n \"task.deleted\",\n \"task.completed\",\n // Comment events\n \"comment.created\",\n \"comment.updated\",\n \"comment.deleted\",\n // File events\n \"file.uploaded\",\n \"file.deleted\",\n // Member events\n \"member.added\",\n \"member.removed\",\n \"member.role_changed\",\n] as const;\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];\n\n// =============================================================================\n// SCOPE DEFINITIONS\n// =============================================================================\n\nexport const AVAILABLE_SCOPES = [\n // User scopes\n {\n scope: \"user:read\",\n description: \"Read user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:write\",\n description: \"Modify user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:email\",\n description: \"Access user email address\",\n category: \"User\",\n },\n // Workspace scopes\n {\n scope: \"workspace:read\",\n description: \"Read workspace information\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:write\",\n description: \"Create and modify workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:delete\",\n description: \"Delete workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:members\",\n description: \"Manage workspace members\",\n category: \"Workspace\",\n },\n // Project scopes\n {\n scope: \"project:read\",\n description: \"Read project information\",\n category: \"Project\",\n },\n {\n scope: \"project:write\",\n description: \"Create and modify projects\",\n category: \"Project\",\n },\n {\n scope: \"project:delete\",\n description: \"Delete projects\",\n category: \"Project\",\n },\n // Task scopes\n { scope: \"task:read\", description: \"Read tasks\", category: \"Task\" },\n {\n scope: \"task:write\",\n description: \"Create and modify tasks\",\n category: \"Task\",\n },\n { scope: \"task:delete\", description: \"Delete tasks\", category: \"Task\" },\n // File scopes\n { scope: \"file:read\", description: \"Read files\", category: \"File\" },\n {\n scope: \"file:write\",\n description: \"Upload and modify files\",\n category: \"File\",\n },\n { scope: \"file:delete\", description: \"Delete files\", category: \"File\" },\n // Comment scopes\n { scope: \"comment:read\", description: \"Read comments\", category: \"Comment\" },\n {\n scope: \"comment:write\",\n description: \"Create and modify comments\",\n category: \"Comment\",\n },\n {\n scope: \"comment:delete\",\n description: \"Delete comments\",\n category: \"Comment\",\n },\n // Offline access\n {\n scope: \"offline_access\",\n description: \"Access data while user is offline\",\n category: \"Access\",\n },\n] as const;\n\nexport type AvailableScope = (typeof AVAILABLE_SCOPES)[number][\"scope\"];\n"],"mappings":";AAm2BA,IAAa,IAAiB,yhBAwC7B,EAQY,IAAmB;CAE9B;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;EAAQ;CACnE;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;EAAQ;CAEvE;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;EAAQ;CACnE;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;EAAQ;CAEvE;EAAE,OAAO;EAAgB,aAAa;EAAiB,UAAU;EAAW;CAC5E;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/devportal/types/index.ts"],"sourcesContent":["/**\n * Developer Portal Types\n *\n * Core type definitions for the Developer Portal microfrontend\n */\n\nimport type { ReactNode } from \"react\";\n\n// =============================================================================\n// NAVIGATION TYPES\n// =============================================================================\n\n/**\n * Navigation item for shell integration\n */\nexport interface NavItem {\n id: string;\n label: string;\n path: string;\n icon?: ReactNode;\n badge?: number;\n order?: number;\n group?: string;\n visible?: boolean;\n children?: NavItem[];\n}\n\n/**\n * Footer item for shell integration\n */\nexport interface FooterItem {\n id: string;\n label: string;\n type: \"status\" | \"action\";\n order?: number;\n section: \"left\" | \"center\" | \"right\";\n}\n\n// =============================================================================\n// ROOT PROPS\n// =============================================================================\n\n/**\n * Props for the DevportalRoot component\n */\nexport interface DevportalRootProps {\n /**\n * Base path for the devportal microfrontend\n */\n basePath?: string;\n\n /**\n * Tenant ID for scoping operations\n */\n tenantId?: string;\n\n /**\n * Workspace ID (optional, for workspace-scoped features)\n */\n workspaceId?: string;\n\n /**\n * API Gateway URL for GraphQL requests (global gateway)\n */\n apiGatewayUrl?: string;\n\n /**\n * Workspace gateway URL for workspace-layer operations (e.g. wspace-files-svc uploads)\n * e.g. \"https://graphqlworkspaces.burdenoff.com\" or \"http://localhost:4003\"\n */\n workspacesGatewayUrl?: string;\n\n /**\n * Authentication token for API requests\n */\n authToken?: string;\n\n /**\n * Workspace authentication token for workspace-gateway requests (X-Workspace-Authorization).\n * May be a cached/stale value — use getWorkspaceToken() for upload flows that need a fresh token.\n */\n workspaceToken?: string;\n\n /**\n * Async getter that always returns a valid (non-expired) workspace token.\n * Call this instead of reading workspaceToken directly for workspace-layer mutations.\n */\n getWorkspaceToken?: () => Promise<string | null>;\n\n /**\n * Navigation function provided by the shell\n */\n navigate?: (path: string) => void;\n\n /**\n * Current user information\n */\n currentUser?: {\n id: string;\n email: string;\n firstName?: string;\n lastName?: string;\n avatar?: string;\n };\n\n /**\n * Optional function to register navigation items with the shell\n */\n registerNavItems?: (items: NavItem | NavItem[]) => () => void;\n\n /**\n * Optional function to register footer items with the shell\n */\n registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;\n\n /**\n * Optional callback when an application is selected\n */\n onApplicationSelect?: (app: Application) => void;\n\n /**\n * Optional callback when an organization is selected\n */\n onOrganizationSelect?: (org: DeveloperOrganization) => void;\n}\n\n// =============================================================================\n// APPLICATION TYPES\n// =============================================================================\n\nexport type AppType =\n | \"WEB_APP\"\n | \"MOBILE_APP\"\n | \"DESKTOP_APP\"\n | \"CLI_TOOL\"\n | \"API_INTEGRATION\"\n | \"WORKFLOW\"\n | \"AUTOMATION\"\n | \"WIDGET\"\n | \"BOT\"\n | \"SERVICE\"\n | \"INTEGRATION\"\n | \"ANALYST\"\n | \"ASSISTANT\"\n | \"SPECIALIST\"\n | \"NODE\"\n | \"VIBEMODULE\"\n | \"CONSOLE\"\n | \"DASHBOARD\"\n | \"PARSER\"\n | \"DATASINK\";\n\nexport type AppStatus =\n | \"DRAFT\"\n | \"SUBMITTED\"\n | \"IN_REVIEW\"\n | \"APPROVED\"\n | \"PUBLISHED\"\n | \"REJECTED\"\n | \"SUSPENDED\"\n | \"DEPRECATED\"\n | \"ACTIVE\"\n | \"INACTIVE\"\n | \"REVOKED\";\n\nexport type AppVisibility = \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\" | \"INTERNAL\";\n\nexport type AppSubmissionReviewStatus =\n | \"PENDING\"\n | \"IN_PROGRESS\"\n | \"APPROVED\"\n | \"REJECTED\"\n | \"CHANGES_REQUESTED\";\n\nexport interface Application {\n id: string;\n name: string;\n slug: string;\n shortDescription?: string;\n description?: string;\n appType: AppType;\n status: AppStatus;\n visibility: AppVisibility;\n tenantId: string;\n developerId: string;\n developerOrganizationId?: string;\n developerOrganization?: DeveloperOrganization;\n category?: string;\n tags: string[];\n keywords: string[];\n iconUrl?: string;\n bannerUrl?: string;\n videoUrl?: string;\n screenshots?: Record<string, unknown>;\n websiteUrl?: string;\n documentationUrl?: string;\n supportUrl?: string;\n privacyPolicyUrl?: string;\n termsOfServiceUrl?: string;\n githubUrl?: string;\n pricingType?: string;\n pricingDetails?: Record<string, unknown>;\n installCount: number;\n activeInstallCount: number;\n averageRating?: number;\n reviewCount: number;\n submittedAt?: string;\n publishedAt?: string;\n lastReviewedAt?: string;\n reviewStatus: AppSubmissionReviewStatus;\n reviewNotes?: string;\n reviewedBy?: string;\n isFeatured: boolean;\n isVerified: boolean;\n requiresApproval: boolean;\n manifest?: Record<string, unknown>;\n config?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n appId?: string;\n createdAt: string;\n updatedAt: string;\n versions?: ApplicationVersion[];\n oauthClients?: OAuthClient[];\n apiKeys?: ApiKey[];\n webhooks?: Webhook[];\n scopes?: ApplicationScope[];\n}\n\nexport interface ApplicationVersion {\n id: string;\n applicationId: string;\n version: string;\n releaseNotes?: string;\n changelog?: string;\n status: string;\n isLatest: boolean;\n isStable: boolean;\n packageUrl?: string;\n packageSize?: string;\n packageHash?: string;\n manifest?: Record<string, unknown>;\n minPlatformVersion?: string;\n maxPlatformVersion?: string;\n requiredScopes: string[];\n publishedAt?: string;\n deprecatedAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ApplicationScope {\n id: string;\n applicationId: string;\n scope: string;\n description?: string;\n isRequired: boolean;\n createdAt: string;\n}\n\n// =============================================================================\n// OAUTH TYPES\n// =============================================================================\n\nexport type OAuthClientType = \"PUBLIC\" | \"CONFIDENTIAL\";\n\nexport type OAuthGrantType =\n | \"AUTHORIZATION_CODE\"\n | \"REFRESH_TOKEN\"\n | \"CLIENT_CREDENTIALS\"\n | \"IMPLICIT\"\n | \"PASSWORD\";\n\nexport interface OAuthClient {\n id: string;\n applicationId: string;\n name: string;\n clientId: string;\n clientSecret: string;\n clientType: OAuthClientType;\n redirectUris: string[];\n allowedGrantTypes: OAuthGrantType[];\n allowedScopes: string[];\n accessTokenLifetime: number;\n refreshTokenLifetime: number;\n isActive: boolean;\n requirePKCE: boolean;\n requireConsent: boolean;\n metadata?: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface OAuthToken {\n id: string;\n clientId: string;\n userId: string;\n tenantId: string;\n workspaceId?: string;\n accessToken: string;\n refreshToken?: string;\n tokenType: string;\n scopes: string[];\n expiresAt: string;\n refreshExpiresAt?: string;\n isRevoked: boolean;\n revokedAt?: string;\n createdAt: string;\n lastUsedAt?: string;\n}\n\n// =============================================================================\n// API KEY TYPES\n// =============================================================================\n\nexport type ApiKeyType = \"PUBLISHABLE\" | \"SECRET\" | \"RESTRICTED\";\n\nexport interface ApiKey {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n keyId: string;\n keyHash?: string; // Hash is not always returned by API for security\n keyPrefix: string;\n keyType: ApiKeyType;\n scopes: string[];\n permissions?: Record<string, unknown>;\n rateLimit?: number;\n quotaLimit?: number;\n quotaUsed: number;\n quotaResetAt?: string;\n allowedIps: string[];\n allowedDomains: string[];\n environment?: string;\n isActive: boolean;\n isRevoked: boolean;\n createdAt: string;\n lastUsedAt?: string;\n expiresAt?: string;\n revokedAt?: string;\n}\n\nexport interface ApiKeyPayload {\n apiKey: ApiKey;\n plainTextKey: string;\n}\n\n// =============================================================================\n// WEBHOOK TYPES\n// =============================================================================\n\nexport type WebhookStatus = \"ACTIVE\" | \"PAUSED\" | \"FAILED\" | \"DISABLED\";\n\nexport type WebhookDeliveryStatus =\n | \"PENDING\"\n | \"DELIVERING\"\n | \"DELIVERED\"\n | \"FAILED\"\n | \"RETRYING\";\n\nexport interface Webhook {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n url: string;\n secret?: string;\n events: string[];\n filters?: Record<string, unknown>;\n retryEnabled: boolean;\n maxRetries: number;\n retryDelay: number;\n status: WebhookStatus;\n isActive: boolean;\n successCount: number;\n failureCount: number;\n lastTriggeredAt?: string;\n lastSuccessAt?: string;\n lastFailureAt?: string;\n lastFailureReason?: string;\n createdAt: string;\n updatedAt: string;\n deliveries?: WebhookDelivery[];\n}\n\nexport interface WebhookDelivery {\n id: string;\n webhookId: string;\n eventType: string;\n eventId: string;\n payload: Record<string, unknown>;\n attemptNumber: number;\n status: WebhookDeliveryStatus;\n requestHeaders?: Record<string, unknown>;\n requestBody?: Record<string, unknown>;\n responseStatus?: number;\n responseHeaders?: Record<string, unknown>;\n responseBody?: string;\n deliveredAt?: string;\n failedAt?: string;\n nextRetryAt?: string;\n errorMessage?: string;\n errorCode?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface WebhookTestResult {\n success: boolean;\n statusCode?: number;\n response?: string;\n error?: string;\n}\n\n// =============================================================================\n// DEVELOPER ORGANIZATION TYPES\n// =============================================================================\n\nexport type DeveloperOrgRole = \"OWNER\" | \"ADMIN\" | \"DEVELOPER\" | \"VIEWER\";\n\nexport type DeveloperOrgTier =\n | \"FREE\"\n | \"DEVELOPER\"\n | \"PROFESSIONAL\"\n | \"ENTERPRISE\";\n\nexport type PartnerTier = \"REGISTERED\" | \"SELECT\" | \"PREMIER\";\n\nexport type InvitationStatus =\n | \"PENDING\"\n | \"ACCEPTED\"\n | \"REJECTED\"\n | \"EXPIRED\"\n | \"CANCELLED\";\n\nexport interface DeveloperOrganization {\n id: string;\n name: string;\n slug: string;\n description?: string;\n logoUrl?: string;\n bannerUrl?: string;\n websiteUrl?: string;\n contactEmail?: string;\n supportEmail?: string;\n tier: DeveloperOrgTier;\n partnerTier?: PartnerTier;\n partnerSince?: string;\n isVerified: boolean;\n verifiedAt?: string;\n verificationMethod?: string;\n isCertified: boolean;\n certifiedAt?: string;\n certificationExpiry?: string;\n totalApps: number;\n publishedApps: number;\n totalInstalls: number;\n activeInstalls: number;\n totalApiCalls: string;\n averageRating?: number;\n totalRevenue: number;\n totalPlatformFees: number;\n totalPayouts: number;\n pendingBalance: number;\n lifetimeRevenue: number;\n platformFeeRate: number;\n apiCallsLimit: number;\n storageLimit: string;\n apiCallsUsed: string;\n storageUsed: string;\n quotaResetAt?: string;\n taxId?: string;\n taxCountry?: string;\n taxFormType?: string;\n taxFormVerified: boolean;\n legalEntityName?: string;\n legalEntityType?: string;\n legalAddress?: Record<string, unknown>;\n settings?: Record<string, unknown>;\n billingAccountId?: string;\n billingLinkedAt?: string;\n createdAt: string;\n updatedAt: string;\n members?: DeveloperOrgMember[];\n invitations?: DeveloperOrgInvitation[];\n applications?: Application[];\n}\n\nexport interface DeveloperOrgMember {\n id: string;\n organizationId: string;\n userId: string;\n email?: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy?: string;\n invitationId?: string;\n isActive: boolean;\n isSuspended: boolean;\n suspendedAt?: string;\n suspendedReason?: string;\n joinedAt: string;\n lastActiveAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DeveloperOrgInvitation {\n id: string;\n organizationId: string;\n invitedUserId?: string;\n invitedEmail: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy: string;\n message?: string;\n token: string;\n status: InvitationStatus;\n expiresAt: string;\n respondedAt?: string;\n memberId?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// ANALYTICS TYPES (MOCKED)\n// =============================================================================\n\nexport interface ApplicationAnalytics {\n id: string;\n applicationId: string;\n date: string;\n period: string;\n activeUsers: number;\n newUsers: number;\n apiCalls: number;\n errors: number;\n avgResponseTime?: number;\n sessions: number;\n avgSessionDuration?: number;\n createdAt: string;\n}\n\nexport interface DeveloperOrgRevenueAnalytics {\n id: string;\n organizationId: string;\n period: string;\n periodStart: string;\n periodEnd: string;\n grossRevenue: number;\n platformFees: number;\n netRevenue: number;\n salesCount: number;\n refundsCount: number;\n refundsAmount: number;\n newSubscribers: number;\n churnedSubscribers: number;\n activeSubscribers: number;\n avgRevenuePerUser: number;\n topApps?: Array<{ appId: string; revenue: number }>;\n createdAt: string;\n}\n\n// =============================================================================\n// MONETIZATION TYPES (MOCKED)\n// =============================================================================\n\nexport type DevPortalPricingModel =\n | \"FREE\"\n | \"FREEMIUM\"\n | \"SUBSCRIPTION\"\n | \"ONE_TIME\"\n | \"USAGE_BASED\"\n | \"ENTERPRISE\";\n\nexport type SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\nexport type PayoutStatus =\n | \"PENDING\"\n | \"PROCESSING\"\n | \"COMPLETED\"\n | \"FAILED\"\n | \"CANCELLED\";\n\nexport interface DeveloperOrgSale {\n id: string;\n organizationId: string;\n applicationId: string;\n buyerUserId: string;\n buyerTenantId?: string;\n installationScope: string;\n pricingModel: DevPortalPricingModel;\n planName?: string;\n grossAmount: number;\n platformFeeRate: number;\n platformFee: number;\n netAmount: number;\n currency: string;\n taxAmount?: number;\n discountAmount?: number;\n status: SaleStatus;\n createdAt: string;\n}\n\nexport interface DeveloperOrgPayout {\n id: string;\n organizationId: string;\n periodStart: string;\n periodEnd: string;\n grossAmount: number;\n platformFees: number;\n netAmount: number;\n currency: string;\n status: PayoutStatus;\n processedAt?: string;\n failedAt?: string;\n failureReason?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// APP INSTALLATION TYPES\n// =============================================================================\n\nexport type AppInstallationStatus =\n | \"ACTIVE\"\n | \"SUSPENDED\"\n | \"TRIAL\"\n | \"EXPIRED\"\n | \"UNINSTALLED\";\n\nexport interface AppInstallation {\n id: string;\n applicationId: string;\n userId: string;\n tenantId?: string;\n workspaceId?: string;\n organizationId?: string;\n scope: string;\n installedVersion: string;\n status: AppInstallationStatus;\n grantedScopes: string[];\n settings?: Record<string, unknown>;\n installedAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// REVIEW TYPES\n// =============================================================================\n\nexport type ReviewStatus =\n | \"PENDING\"\n | \"PUBLISHED\"\n | \"HIDDEN\"\n | \"FLAGGED\"\n | \"REMOVED\";\n\nexport interface AppReview {\n id: string;\n applicationId: string;\n userId: string;\n rating: number;\n title?: string;\n body?: string;\n status: ReviewStatus;\n isFeatured: boolean;\n helpfulCount: number;\n notHelpfulCount: number;\n developerResponse?: string;\n developerResponseAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// DELEGATION TYPES\n// =============================================================================\n\nexport type DelegationStatus = \"ACTIVE\" | \"REVOKED\" | \"EXPIRED\";\n\nexport interface AppUserDelegation {\n id: string;\n appId: string;\n app?: {\n id: string;\n name: string;\n description?: string;\n type: string;\n status: string;\n };\n userId: string;\n scopes: string[];\n workspaceIds: string[];\n status: DelegationStatus;\n grantedAt: string;\n grantedBy: string;\n expiresAt?: string;\n revokedAt?: string;\n revokedBy?: string;\n}\n\nexport interface GrantDelegationInput {\n appId: string;\n userId?: string;\n workspaceIds: string[];\n scopes: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUTH APP TYPES (from auth-svc layer)\n// =============================================================================\n\nexport type AuthAppType =\n | \"ANALYST\"\n | \"API_INTEGRATION\"\n | \"ASSISTANT\"\n | \"AUTOMATION\"\n | \"BOT\"\n | \"CLI_TOOL\"\n | \"DESKTOP_APP\"\n | \"INTEGRATION\"\n | \"MOBILE_APP\"\n | \"SERVICE\"\n | \"SPECIALIST\"\n | \"WEB_APP\"\n | \"WIDGET\"\n | \"WORKFLOW\";\nexport type AuthAppStatus = \"ACTIVE\" | \"INACTIVE\" | \"REVOKED\";\n\nexport interface AuthApp {\n id: string;\n name: string;\n description?: string;\n clientId: string;\n type: AuthAppType;\n status: AuthAppStatus;\n scopes: string[];\n redirectUris: string[];\n ownerUserId: string;\n tenantId: string;\n delegations?: AppUserDelegation[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface AuthAppCreateResponse {\n app: AuthApp;\n clientSecret: string;\n}\n\nexport interface CreateAuthAppInput {\n name: string;\n description?: string;\n type: AuthAppType;\n scopes: string[];\n redirectUris?: string[];\n tenantId?: string;\n}\n\nexport interface UpdateAuthAppInput {\n name?: string;\n description?: string;\n scopes?: string[];\n redirectUris?: string[];\n}\n\n// =============================================================================\n// AUTH API KEY TYPES (from auth-svc layer)\n// =============================================================================\n\nexport interface AuthApiKey {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string;\n createdAt: string;\n expiresAt?: string;\n lastUsedAt?: string;\n}\n\nexport interface AuthApiKeyResult {\n apiKey: string;\n apiSecret: string;\n keyId: string;\n}\n\nexport interface AuthApiKeyRotateResult {\n apiSecret: string;\n}\n\nexport interface GenerateAuthApiKeyInput {\n appId: string;\n name: string;\n scope: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUDIT LOG TYPES\n// =============================================================================\n\nexport interface AuditLog {\n id: string;\n actorId: string;\n actorType: string;\n tenantId: string;\n action: string;\n resource: string;\n resourceId?: string;\n changes?: Record<string, unknown>;\n ipAddress?: string;\n userAgent?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// VIEW TYPES\n// =============================================================================\n\nexport type DevportalView =\n | \"dashboard\"\n | \"organizations\"\n | \"apps\"\n | \"oauth\"\n | \"api-keys\"\n | \"webhooks\"\n | \"delegations\"\n | \"analytics\"\n | \"revenue\"\n | \"docs\"\n | \"profile\"\n | \"settings\";\n\n// =============================================================================\n// FILTER TYPES\n// =============================================================================\n\nexport interface ApplicationFilter {\n search?: string;\n status?: AppStatus;\n visibility?: AppVisibility;\n appType?: AppType;\n organizationId?: string;\n isFeatured?: boolean;\n}\n\nexport interface WebhookFilter {\n status?: WebhookStatus;\n event?: string;\n}\n\n// =============================================================================\n// WEBHOOK EVENT TYPES\n// =============================================================================\n\nexport const WEBHOOK_EVENTS = [\n // Application events\n \"application.created\",\n \"application.updated\",\n \"application.deleted\",\n \"application.published\",\n \"application.unpublished\",\n \"application.suspended\",\n // Installation events\n \"installation.created\",\n \"installation.updated\",\n \"installation.deleted\",\n // User events\n \"user.created\",\n \"user.updated\",\n \"user.deleted\",\n // Workspace events\n \"workspace.created\",\n \"workspace.updated\",\n \"workspace.deleted\",\n // Project events\n \"project.created\",\n \"project.updated\",\n \"project.deleted\",\n // Task events\n \"task.created\",\n \"task.updated\",\n \"task.deleted\",\n \"task.completed\",\n // Comment events\n \"comment.created\",\n \"comment.updated\",\n \"comment.deleted\",\n // File events\n \"file.uploaded\",\n \"file.deleted\",\n // Member events\n \"member.added\",\n \"member.removed\",\n \"member.role_changed\",\n] as const;\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];\n\n// =============================================================================\n// SCOPE DEFINITIONS\n// =============================================================================\n\nexport const AVAILABLE_SCOPES = [\n // User scopes\n {\n scope: \"user:read\",\n description: \"Read user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:write\",\n description: \"Modify user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:email\",\n description: \"Access user email address\",\n category: \"User\",\n },\n // Workspace scopes\n {\n scope: \"workspace:read\",\n description: \"Read workspace information\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:write\",\n description: \"Create and modify workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:delete\",\n description: \"Delete workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:members\",\n description: \"Manage workspace members\",\n category: \"Workspace\",\n },\n // Project scopes\n {\n scope: \"project:read\",\n description: \"Read project information\",\n category: \"Project\",\n },\n {\n scope: \"project:write\",\n description: \"Create and modify projects\",\n category: \"Project\",\n },\n {\n scope: \"project:delete\",\n description: \"Delete projects\",\n category: \"Project\",\n },\n // Task scopes\n { scope: \"task:read\", description: \"Read tasks\", category: \"Task\" },\n {\n scope: \"task:write\",\n description: \"Create and modify tasks\",\n category: \"Task\",\n },\n { scope: \"task:delete\", description: \"Delete tasks\", category: \"Task\" },\n // File scopes\n { scope: \"file:read\", description: \"Read files\", category: \"File\" },\n {\n scope: \"file:write\",\n description: \"Upload and modify files\",\n category: \"File\",\n },\n { scope: \"file:delete\", description: \"Delete files\", category: \"File\" },\n // Comment scopes\n { scope: \"comment:read\", description: \"Read comments\", category: \"Comment\" },\n {\n scope: \"comment:write\",\n description: \"Create and modify comments\",\n category: \"Comment\",\n },\n {\n scope: \"comment:delete\",\n description: \"Delete comments\",\n category: \"Comment\",\n },\n // Offline access\n {\n scope: \"offline_access\",\n description: \"Access data while user is offline\",\n category: \"Access\",\n },\n] as const;\n\nexport type AvailableScope = (typeof AVAILABLE_SCOPES)[number][\"scope\"];\n"],"mappings":";AAm2BA,IAAa,IAAiB,yhBAwC7B,EAQY,IAAmB;CAE9B;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;EAAQ;CACnE;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;EAAQ;CAEvE;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;EAAQ;CACnE;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;EAAQ;CAEvE;EAAE,OAAO;EAAgB,aAAa;EAAiB,UAAU;EAAW;CAC5E;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACD;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CAED;EACE,OAAO;EACP,aAAa;EACb,UAAU;EACX;CACF"}