@burdenoff/microfe-devportal 2026.625.10 → 2026.626.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) 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.map +1 -1
  10. package/dist/devportal/components/SelectionCheckbox.js.map +1 -1
  11. package/dist/devportal/hooks/usePermission.js +2 -2
  12. package/dist/devportal/hooks/usePermission.js.map +1 -1
  13. package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
  14. package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
  15. package/dist/devportal/pages/CreateApplicationPage.js +185 -104
  16. package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
  17. package/dist/devportal/pages/CreateWebhookPage.js +6 -5
  18. package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
  19. package/dist/devportal/pages/DashboardPage.js.map +1 -1
  20. package/dist/devportal/pages/OAuthCallbackPage.js +64 -0
  21. package/dist/devportal/pages/OAuthCallbackPage.js.map +1 -0
  22. package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
  23. package/dist/devportal/pages/OrganizationDetailPage.js.map +1 -1
  24. package/dist/devportal/pages/PublishPage.js +392 -187
  25. package/dist/devportal/pages/PublishPage.js.map +1 -1
  26. package/dist/devportal/pages/ResourcesPage.js.map +1 -1
  27. package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -1
  28. package/dist/devportal/pages/WebhookDetailPage.js.map +1 -1
  29. package/dist/devportal/pages/WebhooksPage.js +1 -1
  30. package/dist/devportal/pages/WebhooksPage.js.map +1 -1
  31. package/dist/devportal/types/index.js.map +1 -1
  32. package/dist/generated/global-operations.js +260 -174
  33. package/dist/generated/global-operations.js.map +1 -1
  34. package/dist/node_modules/@apollo/client/version.js +1 -1
  35. package/dist/node_modules/@apollo/client/version.js.map +1 -1
  36. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CreateApplicationPage.js","names":[],"sources":["../../../src/devportal/pages/CreateApplicationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport {\n ArrowLeft,\n Box,\n Check,\n ChevronRight,\n GitFork,\n Info,\n Loader2,\n Puzzle,\n Save,\n X,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalEventBus } from \"../events\";\nimport { cn } from \"../utils/cn\";\nimport {\n useCreateApplicationMutation,\n useGetMyOrganizationsQuery,\n} from \"../../generated/global-operations\";\n\n// ─── Builder types ────────────────────────────────────────────────────────────\n\ntype BuilderType = \"app\" | \"node\" | \"workflow\";\n\n// ─── Permissions (App type) ───────────────────────────────────────────────────\n\nconst RBAC_RESOURCES = [\n {\n id: \"auth\",\n label: \"Authentication\",\n description: \"User identity and session management\",\n },\n {\n id: \"billing\",\n label: \"Billing\",\n description: \"Billing accounts and subscriptions\",\n },\n {\n id: \"files\",\n label: \"Files\",\n description: \"File storage and management\",\n },\n {\n id: \"tags\",\n label: \"Tags\",\n description: \"Tags and labels on resources\",\n },\n {\n id: \"calendar\",\n label: \"Calendar\",\n description: \"Events and scheduling\",\n },\n {\n id: \"workflows\",\n label: \"Workflows\",\n description: \"Automation workflows\",\n },\n {\n id: \"channels\",\n label: \"Channels\",\n description: \"Messaging channels\",\n },\n {\n id: \"notifications\",\n label: \"Notifications\",\n description: \"Platform notifications\",\n },\n {\n id: \"users\",\n label: \"Users & Members\",\n description: \"Workspace members and profiles\",\n },\n {\n id: \"workspaces\",\n label: \"Workspaces\",\n description: \"Workspace settings and metadata\",\n },\n {\n id: \"content\",\n label: \"Content\",\n description: \"Pages, documents, and content\",\n },\n {\n id: \"analytics\",\n label: \"Analytics\",\n description: \"Usage analytics and reporting\",\n },\n {\n id: \"webhooks\",\n label: \"Webhooks\",\n description: \"Webhook subscriptions\",\n },\n {\n id: \"rbac\",\n label: \"Roles & Permissions\",\n description: \"Access control configuration\",\n },\n] as const;\n\ntype ResourceId = (typeof RBAC_RESOURCES)[number][\"id\"];\ntype PermissionLevel = \"none\" | \"read\" | \"write\";\n\ntype PermissionMap = Record<ResourceId, PermissionLevel>;\n\nfunction defaultPermissions(): PermissionMap {\n return Object.fromEntries(\n RBAC_RESOURCES.map((r) => [r.id, \"none\"]),\n ) as PermissionMap;\n}\n\nfunction permissionsToManifest(perms: PermissionMap) {\n const entries = Object.entries(perms).filter(([, level]) => level !== \"none\");\n return entries.map(([resource, level]) => ({ resource, level }));\n}\n\n// ─── Org options ──────────────────────────────────────────────────────────────\n\nconst VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public — visible to everyone\" },\n { value: \"PRIVATE\", label: \"Private — only visible to you\" },\n { value: \"UNLISTED\", label: \"Unlisted — accessible via direct link\" },\n];\n\n// ─── Sub-components ───────────────────────────────────────────────────────────\n\ninterface TypeCardProps {\n icon: FC<{ size?: number; className?: string }>;\n title: string;\n description: string;\n bullets: string[];\n selected: boolean;\n onClick: () => void;\n}\n\nconst TypeCard: FC<TypeCardProps> = ({\n icon: Icon,\n title,\n description,\n bullets,\n selected,\n onClick,\n}) => (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"relative flex flex-col gap-3 rounded-xl border-2 p-6 text-left transition-all hover:border-primary/50 hover:shadow-sm\",\n selected\n ? \"border-primary bg-action-primary-bg/5 shadow-sm\"\n : \"border-border-subtle bg-bg-surface\",\n )}\n >\n {selected && (\n <span className=\"absolute top-4 right-4 flex h-5 w-5 items-center justify-center rounded-full bg-action-primary-bg text-action-primary-text\">\n <Check size={12} />\n </span>\n )}\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-bg-sunken\">\n <Icon size={22} className=\"text-text-primary\" />\n </div>\n <div>\n <p className=\"font-semibold text-text-primary\">{title}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>\n </div>\n <ul className=\"mt-1 space-y-1\">\n {bullets.map((b) => (\n <li\n key={b}\n className=\"flex items-center gap-2 text-xs text-text-secondary\"\n >\n <ChevronRight size={12} className=\"flex-shrink-0 text-primary\" />\n {b}\n </li>\n ))}\n </ul>\n </button>\n);\n\n// ─── Permissions matrix ───────────────────────────────────────────────────────\n\ninterface PermissionsMatrixProps {\n permissions: PermissionMap;\n onChange: (resource: ResourceId, level: PermissionLevel) => void;\n}\n\nconst PermissionsMatrix: FC<PermissionsMatrixProps> = ({\n permissions,\n onChange,\n}) => {\n const levels: PermissionLevel[] = [\"none\", \"read\", \"write\"];\n const levelLabels: Record<PermissionLevel, string> = {\n none: \"No access\",\n read: \"Read\",\n write: \"Read & Write\",\n };\n\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border-subtle\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken/50\">\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary w-1/2\">\n Resource\n </th>\n {levels.map((l) => (\n <th\n key={l}\n className=\"px-4 py-3 text-center font-medium text-text-secondary\"\n >\n {levelLabels[l]}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {RBAC_RESOURCES.map((resource) => (\n <tr\n key={resource.id}\n className=\"hover:bg-bg-sunken/30 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <p className=\"font-medium text-text-primary\">{resource.label}</p>\n <p className=\"text-xs text-text-secondary\">\n {resource.description}\n </p>\n </td>\n {levels.map((level) => (\n <td key={level} className=\"px-4 py-3 text-center\">\n <input\n type=\"radio\"\n name={`permission-${resource.id}`}\n value={level}\n checked={permissions[resource.id] === level}\n onChange={() => onChange(resource.id, level)}\n className=\"h-4 w-4 accent-primary cursor-pointer\"\n />\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n};\n\n// ─── Main component ───────────────────────────────────────────────────────────\n\ninterface FormData {\n name: string;\n slug: string;\n shortDescription: string;\n description: string;\n visibility: string;\n developerOrganizationId: string;\n websiteUrl: string;\n githubUrl: string;\n documentationUrl: string;\n supportUrl: string;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n shortDescription?: string;\n}\n\nexport const CreateApplicationPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const bus = useDevportalEventBus();\n\n const [builderType, setBuilderType] = useState<BuilderType | null>(null);\n const [permissions, setPermissions] =\n useState<PermissionMap>(defaultPermissions());\n const [formData, setFormData] = useState<FormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n visibility: \"PRIVATE\",\n developerOrganizationId: activeOrgId ?? \"\",\n websiteUrl: \"\",\n githubUrl: \"\",\n documentationUrl: \"\",\n supportUrl: \"\",\n });\n const [errors, setErrors] = useState<FormErrors>({});\n\n const [createApplication, { loading: isSubmitting }] =\n useCreateApplicationMutation();\n const { data: orgsData } = useGetMyOrganizationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const orgOptions = [\n { value: \"\", label: \"No organization (personal)\" },\n ...(orgsData?.myDeveloperOrganizations ?? []).map(\n (org: { id: string; name: string }) => ({\n value: org.id,\n label: org.name,\n }),\n ),\n ];\n\n const handleNav = (path: string) => navigate?.(`${basePath}${path}`);\n\n const generateSlug = (name: string) =>\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n\n const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setFormData((prev) => ({\n ...prev,\n name,\n slug: prev.slug || generateSlug(name),\n }));\n if (errors.name) setErrors((p) => ({ ...p, name: undefined }));\n };\n\n const handleChange = (field: keyof FormData, value: string) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors])\n setErrors((p) => ({ ...p, [field]: undefined }));\n };\n\n const handlePermissionChange = (\n resource: ResourceId,\n level: PermissionLevel,\n ) => {\n setPermissions((prev) => ({ ...prev, [resource]: level }));\n };\n\n const validate = (): boolean => {\n const newErrors: FormErrors = {};\n if (!formData.name.trim()) {\n newErrors.name = \"Name is required\";\n } else if (formData.name.length < 3) {\n newErrors.name = \"Name must be at least 3 characters\";\n } else if (formData.name.length > 100) {\n newErrors.name = \"Name must be less than 100 characters\";\n }\n if (!formData.slug.trim()) {\n newErrors.slug = \"Slug is required\";\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug =\n \"Slug can only contain lowercase letters, numbers, and hyphens\";\n }\n if (!formData.shortDescription.trim()) {\n newErrors.shortDescription = \"Short description is required\";\n } else if (formData.shortDescription.length > 200) {\n newErrors.shortDescription =\n \"Short description must be less than 200 characters\";\n }\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const appTypeForBuilder = () => {\n if (builderType === \"node\") return \"NODE\" as const;\n if (builderType === \"workflow\") return \"WORKFLOW\" as const;\n return \"API_INTEGRATION\" as const;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate() || !builderType) return;\n\n const permManifest =\n builderType === \"app\"\n ? { permissions: permissionsToManifest(permissions) }\n : null;\n\n try {\n const result = await createApplication({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n shortDescription: formData.shortDescription || undefined,\n description: formData.description || undefined,\n appType: appTypeForBuilder(),\n visibility: formData.visibility as\n | \"PUBLIC\"\n | \"PRIVATE\"\n | \"UNLISTED\"\n | undefined,\n developerOrganizationId:\n formData.developerOrganizationId || undefined,\n websiteUrl: formData.websiteUrl || undefined,\n githubUrl: formData.githubUrl || undefined,\n documentationUrl: formData.documentationUrl || undefined,\n supportUrl: formData.supportUrl || undefined,\n manifest: permManifest ?? undefined,\n },\n },\n });\n if (result.data?.createApplication?.id) {\n bus.emit(\"devportal.app.created\", {\n appId: result.data.createApplication.id,\n name: formData.name,\n appType: appTypeForBuilder(),\n organizationId: formData.developerOrganizationId || null,\n });\n handleNav(`/apps/${result.data.createApplication.id}`);\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (\n msg.includes(\"slug\") ||\n msg.includes(\"already exists\") ||\n msg.includes(\"slugConflict\")\n ) {\n setErrors((p) => ({\n ...p,\n slug: \"This slug is already in use — try a different name\",\n }));\n }\n }\n };\n\n // ── Step 1: type picker ─────────────────────────────────────────────────────\n if (!builderType) {\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title=\"What do you want to build?\"\n description=\"Choose a builder type to get started\"\n purpose=\"Pick the kind of integration you are creating — a standard app, a Node package, or a workflow. Your choice sets up the right defaults (credentials, permissions, and where it can be installed) so you only fill in what that type actually needs.\"\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <div className=\"max-w-3xl space-y-6\">\n <div className=\"grid gap-4 sm:grid-cols-3\">\n <TypeCard\n icon={Box}\n title=\"App\"\n description=\"Build integrations that connect your service to the platform using OAuth tokens.\"\n bullets={[\n \"GitHub-style permissions\",\n \"OAuth token flow\",\n \"Webhook events\",\n \"Publish to app store\",\n ]}\n selected={builderType === \"app\"}\n onClick={() => setBuilderType(\"app\")}\n />\n <TypeCard\n icon={Puzzle}\n title=\"Node\"\n description=\"Build custom FluidGrids nodes that users can drag into their visual workflows.\"\n bullets={[\n \"ZIP bundle + manifest\",\n \"Custom inputs/outputs\",\n \"Versioned releases\",\n \"Publish to node store\",\n ]}\n selected={builderType === \"node\"}\n onClick={() => setBuilderType(\"node\")}\n />\n <TypeCard\n icon={GitFork}\n title=\"Workflow\"\n description=\"Build reusable automation workflows that users can import and customize.\"\n bullets={[\n \"Manifest + workflow JSON\",\n \"Trigger definitions\",\n \"Versioned releases\",\n \"Publish to workflow store\",\n ]}\n selected={builderType === \"workflow\"}\n onClick={() => setBuilderType(\"workflow\")}\n />\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 flex gap-3 text-sm text-text-secondary\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <p>\n Apps use OAuth to interact with platform data. Nodes and\n Workflows are packaged files you upload after creation.\n </p>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // ── Step 2: form ────────────────────────────────────────────────────────────\n const typeLabel =\n builderType === \"app\"\n ? \"App\"\n : builderType === \"node\"\n ? \"Node\"\n : \"Workflow\";\n const typeIcon =\n builderType === \"app\" ? (\n <Box size={16} />\n ) : builderType === \"node\" ? (\n <Puzzle size={16} />\n ) : (\n <GitFork size={16} />\n );\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title={`Create ${typeLabel}`}\n description={\n builderType === \"app\"\n ? \"Register a new app integration with OAuth permissions\"\n : builderType === \"node\"\n ? \"Register a new FluidGrids node package\"\n : \"Register a new reusable automation workflow\"\n }\n purpose=\"Give your integration a name, description, and the permissions it will request. Once created you will get credentials and can configure webhooks, then publish it to the marketplace when it is ready for users.\"\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Type badge + back to picker */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setBuilderType(null)}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft size={14} />\n Change type\n </button>\n <span className=\"flex items-center gap-1.5 rounded-full border border-border-subtle bg-bg-sunken px-3 py-1 text-xs font-medium\">\n {typeIcon}\n {typeLabel}\n </span>\n </div>\n\n {/* Basic info */}\n <PageSection\n title=\"Basic Information\"\n description=\"Core details about your application\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Name\"\n value={formData.name}\n onChange={handleNameChange}\n placeholder={\n builderType === \"app\"\n ? \"My Integration\"\n : builderType === \"node\"\n ? \"HTTP Request\"\n : \"Onboarding Flow\"\n }\n required\n error={errors.name}\n />\n <InputField\n label=\"Slug\"\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder=\"my-integration\"\n required\n error={errors.slug}\n hint=\"URL-friendly identifier (auto-generated from name)\"\n />\n <TextareaField\n label=\"Short Description\"\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder=\"Briefly describe what this does\"\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 characters`}\n rows={2}\n />\n <TextareaField\n label=\"Full Description\"\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder=\"Detailed description, features, and use cases...\"\n rows={5}\n hint=\"Markdown supported\"\n />\n </div>\n </PageSection>\n\n {/* Visibility + org */}\n <PageSection\n title=\"Publish Settings\"\n description=\"Control who can see this and which organization it belongs to\"\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label=\"Visibility\"\n value={formData.visibility}\n onChange={(e) => handleChange(\"visibility\", e.target.value)}\n options={VISIBILITY_OPTIONS}\n />\n <SelectField\n label=\"Developer Organization\"\n value={formData.developerOrganizationId}\n onChange={(e) =>\n handleChange(\"developerOrganizationId\", e.target.value)\n }\n options={orgOptions}\n />\n </div>\n </PageSection>\n\n {/* Links */}\n <PageSection title=\"Links\" description=\"External resources\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label=\"Website URL\"\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder=\"https://example.com\"\n />\n <InputField\n label=\"GitHub URL\"\n type=\"url\"\n value={formData.githubUrl}\n onChange={(e) => handleChange(\"githubUrl\", e.target.value)}\n placeholder=\"https://github.com/org/repo\"\n />\n <InputField\n label=\"Documentation URL\"\n type=\"url\"\n value={formData.documentationUrl}\n onChange={(e) =>\n handleChange(\"documentationUrl\", e.target.value)\n }\n placeholder=\"https://docs.example.com\"\n />\n <InputField\n label=\"Support URL\"\n type=\"url\"\n value={formData.supportUrl}\n onChange={(e) => handleChange(\"supportUrl\", e.target.value)}\n placeholder=\"https://support.example.com\"\n />\n </div>\n </PageSection>\n\n {/* Permissions — App type only */}\n {builderType === \"app\" && (\n <PageSection\n title=\"Permissions\"\n description=\"Choose what data your app can access. Users will see these permissions when authorizing your app — like GitHub Apps.\"\n >\n <PermissionsMatrix\n permissions={permissions}\n onChange={handlePermissionChange}\n />\n <div className=\"mt-3 flex gap-2 rounded-lg border border-border-subtle bg-bg-sunken/30 p-3 text-xs text-text-secondary\">\n <Info size={14} className=\"flex-shrink-0 mt-0.5\" />\n <span>\n <strong>Read &amp; Write</strong> implies Read. Select only\n what your app genuinely needs — fewer permissions build more\n user trust.\n </span>\n </div>\n </PageSection>\n )}\n\n {/* Node/Workflow file upload note */}\n {builderType !== \"app\" && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 flex gap-3 text-sm text-text-secondary\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"font-medium text-text-primary\">File uploads</p>\n <p className=\"mt-1\">\n {builderType === \"node\"\n ? \"After creating your Node, go to the Versions tab to upload your ZIP bundle and manifest.json.\"\n : \"After creating your Workflow, go to the Versions tab to upload your manifest and workflow JSON definition.\"}\n </p>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={() => handleNav(\"/apps\")}\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\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\"\n >\n {isSubmitting ? (\n <>\n <Loader2 size={16} className=\"animate-spin\" />\n Creating…\n </>\n ) : (\n <>\n <Save size={16} />\n Create {typeLabel}\n </>\n )}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreateApplicationPage.js","names":[],"sources":["../../../src/devportal/pages/CreateApplicationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport {\n ArrowLeft,\n Box,\n Check,\n ChevronRight,\n GitFork,\n Info,\n Loader2,\n Puzzle,\n Save,\n X,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalEventBus } from \"../events\";\nimport { cn } from \"../utils/cn\";\nimport {\n useCreateApplicationMutation,\n useGetMyOrganizationsQuery,\n} from \"../../generated/global-operations\";\n\n// ─── Builder types ────────────────────────────────────────────────────────────\n\ntype BuilderType = \"app\" | \"node\" | \"workflow\";\n\n// ─── Permissions (App type) ───────────────────────────────────────────────────\n\nconst RBAC_RESOURCES = [\n {\n id: \"auth\",\n label: \"Authentication\",\n description: \"User identity and session management\",\n },\n {\n id: \"billing\",\n label: \"Billing\",\n description: \"Billing accounts and subscriptions\",\n },\n {\n id: \"files\",\n label: \"Files\",\n description: \"File storage and management\",\n },\n {\n id: \"tags\",\n label: \"Tags\",\n description: \"Tags and labels on resources\",\n },\n {\n id: \"calendar\",\n label: \"Calendar\",\n description: \"Events and scheduling\",\n },\n {\n id: \"workflows\",\n label: \"Workflows\",\n description: \"Automation workflows\",\n },\n {\n id: \"channels\",\n label: \"Channels\",\n description: \"Messaging channels\",\n },\n {\n id: \"notifications\",\n label: \"Notifications\",\n description: \"Platform notifications\",\n },\n {\n id: \"users\",\n label: \"Users & Members\",\n description: \"Workspace members and profiles\",\n },\n {\n id: \"workspaces\",\n label: \"Workspaces\",\n description: \"Workspace settings and metadata\",\n },\n {\n id: \"content\",\n label: \"Content\",\n description: \"Pages, documents, and content\",\n },\n {\n id: \"analytics\",\n label: \"Analytics\",\n description: \"Usage analytics and reporting\",\n },\n {\n id: \"webhooks\",\n label: \"Webhooks\",\n description: \"Webhook subscriptions\",\n },\n {\n id: \"rbac\",\n label: \"Roles & Permissions\",\n description: \"Access control configuration\",\n },\n] as const;\n\ntype ResourceId = (typeof RBAC_RESOURCES)[number][\"id\"];\ntype PermissionLevel = \"none\" | \"read\" | \"write\";\n\ntype PermissionMap = Record<ResourceId, PermissionLevel>;\n\nfunction defaultPermissions(): PermissionMap {\n return Object.fromEntries(\n RBAC_RESOURCES.map((r) => [r.id, \"none\"]),\n ) as PermissionMap;\n}\n\nfunction permissionsToManifest(perms: PermissionMap) {\n const entries = Object.entries(perms).filter(([, level]) => level !== \"none\");\n return entries.map(([resource, level]) => ({ resource, level }));\n}\n\n// ─── Org options ──────────────────────────────────────────────────────────────\n\nconst VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public — visible to everyone\" },\n { value: \"PRIVATE\", label: \"Private — only visible to you\" },\n { value: \"UNLISTED\", label: \"Unlisted — accessible via direct link\" },\n];\n\nconst CATEGORY_OPTIONS = [\n { value: \"ai\", label: \"AI & Automation\" },\n { value: \"integration\", label: \"Action in an App\" },\n { value: \"data\", label: \"Data & Transform\" },\n { value: \"flow\", label: \"Flow & Logic\" },\n { value: \"core\", label: \"Core\" },\n { value: \"communication\", label: \"Communication\" },\n { value: \"trigger\", label: \"Triggers\" },\n];\n\n// ─── Sub-components ───────────────────────────────────────────────────────────\n\ninterface TypeCardProps {\n icon: FC<{ size?: number; className?: string }>;\n title: string;\n description: string;\n bullets: string[];\n selected: boolean;\n onClick: () => void;\n}\n\nconst TypeCard: FC<TypeCardProps> = ({\n icon: Icon,\n title,\n description,\n bullets,\n selected,\n onClick,\n}) => (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"relative flex flex-col gap-3 rounded-xl border-2 p-6 text-left transition-all hover:border-primary/50 hover:shadow-sm\",\n selected\n ? \"border-primary bg-action-primary-bg/5 shadow-sm\"\n : \"border-border-subtle bg-bg-surface\",\n )}\n >\n {selected && (\n <span className=\"absolute top-4 right-4 flex h-5 w-5 items-center justify-center rounded-full bg-action-primary-bg text-action-primary-text\">\n <Check size={12} />\n </span>\n )}\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-bg-sunken\">\n <Icon size={22} className=\"text-text-primary\" />\n </div>\n <div>\n <p className=\"font-semibold text-text-primary\">{title}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>\n </div>\n <ul className=\"mt-1 space-y-1\">\n {bullets.map((b) => (\n <li\n key={b}\n className=\"flex items-center gap-2 text-xs text-text-secondary\"\n >\n <ChevronRight size={12} className=\"flex-shrink-0 text-primary\" />\n {b}\n </li>\n ))}\n </ul>\n </button>\n);\n\n// ─── Permissions matrix ───────────────────────────────────────────────────────\n\ninterface PermissionsMatrixProps {\n permissions: PermissionMap;\n onChange: (resource: ResourceId, level: PermissionLevel) => void;\n}\n\nconst PermissionsMatrix: FC<PermissionsMatrixProps> = ({\n permissions,\n onChange,\n}) => {\n const levels: PermissionLevel[] = [\"none\", \"read\", \"write\"];\n const levelLabels: Record<PermissionLevel, string> = {\n none: \"No access\",\n read: \"Read\",\n write: \"Read & Write\",\n };\n\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border-subtle\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken/50\">\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary w-1/2\">\n Resource\n </th>\n {levels.map((l) => (\n <th\n key={l}\n className=\"px-4 py-3 text-center font-medium text-text-secondary\"\n >\n {levelLabels[l]}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {RBAC_RESOURCES.map((resource) => (\n <tr\n key={resource.id}\n className=\"hover:bg-bg-sunken/30 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <p className=\"font-medium text-text-primary\">\n {resource.label}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {resource.description}\n </p>\n </td>\n {levels.map((level) => (\n <td key={level} className=\"px-4 py-3 text-center\">\n <input\n type=\"radio\"\n name={`permission-${resource.id}`}\n value={level}\n checked={permissions[resource.id] === level}\n onChange={() => onChange(resource.id, level)}\n className=\"h-4 w-4 accent-primary cursor-pointer\"\n />\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n};\n\n// ─── Main component ───────────────────────────────────────────────────────────\n\ninterface FormData {\n name: string;\n slug: string;\n shortDescription: string;\n description: string;\n category: string;\n visibility: string;\n developerOrganizationId: string;\n websiteUrl: string;\n githubUrl: string;\n documentationUrl: string;\n supportUrl: string;\n oauthAuthUrl: string;\n oauthTokenUrl: string;\n oauthRevokeUrl: string;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n shortDescription?: string;\n}\n\nexport const CreateApplicationPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const bus = useDevportalEventBus();\n\n const [builderType, setBuilderType] = useState<BuilderType | null>(null);\n const [permissions, setPermissions] =\n useState<PermissionMap>(defaultPermissions());\n const [formData, setFormData] = useState<FormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n category: \"\",\n visibility: \"PRIVATE\",\n developerOrganizationId: activeOrgId ?? \"\",\n websiteUrl: \"\",\n githubUrl: \"\",\n documentationUrl: \"\",\n supportUrl: \"\",\n oauthAuthUrl: \"\",\n oauthTokenUrl: \"\",\n oauthRevokeUrl: \"\",\n });\n const [errors, setErrors] = useState<FormErrors>({});\n\n const [createApplication, { loading: isSubmitting }] =\n useCreateApplicationMutation();\n const { data: orgsData } = useGetMyOrganizationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const orgOptions = [\n { value: \"\", label: \"No organization (personal)\" },\n ...(orgsData?.myDeveloperOrganizations ?? []).map(\n (org: { id: string; name: string }) => ({\n value: org.id,\n label: org.name,\n }),\n ),\n ];\n\n const handleNav = (path: string) => navigate?.(`${basePath}${path}`);\n\n const generateSlug = (name: string) =>\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n\n const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setFormData((prev) => ({\n ...prev,\n name,\n slug: prev.slug || generateSlug(name),\n }));\n if (errors.name) setErrors((p) => ({ ...p, name: undefined }));\n };\n\n const handleChange = (field: keyof FormData, value: string) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors])\n setErrors((p) => ({ ...p, [field]: undefined }));\n };\n\n const handlePermissionChange = (\n resource: ResourceId,\n level: PermissionLevel,\n ) => {\n setPermissions((prev) => ({ ...prev, [resource]: level }));\n };\n\n const validate = (): boolean => {\n const newErrors: FormErrors = {};\n if (!formData.name.trim()) {\n newErrors.name = \"Name is required\";\n } else if (formData.name.length < 3) {\n newErrors.name = \"Name must be at least 3 characters\";\n } else if (formData.name.length > 100) {\n newErrors.name = \"Name must be less than 100 characters\";\n }\n if (!formData.slug.trim()) {\n newErrors.slug = \"Slug is required\";\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug =\n \"Slug can only contain lowercase letters, numbers, and hyphens\";\n }\n if (!formData.shortDescription.trim()) {\n newErrors.shortDescription = \"Short description is required\";\n } else if (formData.shortDescription.length > 200) {\n newErrors.shortDescription =\n \"Short description must be less than 200 characters\";\n }\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const appTypeForBuilder = () => {\n if (builderType === \"node\") return \"NODE\" as const;\n if (builderType === \"workflow\") return \"WORKFLOW\" as const;\n return \"API_INTEGRATION\" as const;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate() || !builderType) return;\n\n const permManifest =\n builderType === \"app\"\n ? { permissions: permissionsToManifest(permissions) }\n : null;\n\n try {\n const result = await createApplication({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n shortDescription: formData.shortDescription || undefined,\n description: formData.description || undefined,\n category: formData.category || undefined,\n appType: appTypeForBuilder(),\n visibility: formData.visibility as\n | \"PUBLIC\"\n | \"PRIVATE\"\n | \"UNLISTED\"\n | undefined,\n developerOrganizationId:\n formData.developerOrganizationId || undefined,\n websiteUrl: formData.websiteUrl || undefined,\n githubUrl: formData.githubUrl || undefined,\n documentationUrl: formData.documentationUrl || undefined,\n supportUrl: formData.supportUrl || undefined,\n manifest: permManifest ?? undefined,\n ...(builderType === \"app\" && {\n oauthAuthUrl: formData.oauthAuthUrl || undefined,\n oauthTokenUrl: formData.oauthTokenUrl || undefined,\n oauthRevokeUrl: formData.oauthRevokeUrl || undefined,\n }),\n },\n },\n });\n if (result.data?.createApplication?.id) {\n bus.emit(\"devportal.app.created\", {\n appId: result.data.createApplication.id,\n name: formData.name,\n appType: appTypeForBuilder(),\n organizationId: formData.developerOrganizationId || null,\n });\n handleNav(`/apps/${result.data.createApplication.id}`);\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (\n msg.includes(\"slug\") ||\n msg.includes(\"already exists\") ||\n msg.includes(\"slugConflict\")\n ) {\n setErrors((p) => ({\n ...p,\n slug: \"This slug is already in use — try a different name\",\n }));\n }\n }\n };\n\n // ── Step 1: type picker ─────────────────────────────────────────────────────\n if (!builderType) {\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title=\"What do you want to build?\"\n description=\"Choose a builder type to get started\"\n purpose=\"Pick the kind of integration you are creating — a standard app, a Node package, or a workflow. Your choice sets up the right defaults (credentials, permissions, and where it can be installed) so you only fill in what that type actually needs.\"\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <div className=\"max-w-3xl space-y-6\">\n <div className=\"grid gap-4 sm:grid-cols-3\">\n <TypeCard\n icon={Box}\n title=\"App\"\n description=\"Build integrations that connect your service to the platform using OAuth tokens.\"\n bullets={[\n \"GitHub-style permissions\",\n \"OAuth token flow\",\n \"Webhook events\",\n \"Publish to app store\",\n ]}\n selected={builderType === \"app\"}\n onClick={() => setBuilderType(\"app\")}\n />\n <TypeCard\n icon={Puzzle}\n title=\"Node\"\n description=\"Build custom FluidGrids nodes that users can drag into their visual workflows.\"\n bullets={[\n \"ZIP bundle + manifest\",\n \"Custom inputs/outputs\",\n \"Versioned releases\",\n \"Publish to node store\",\n ]}\n selected={builderType === \"node\"}\n onClick={() => setBuilderType(\"node\")}\n />\n <TypeCard\n icon={GitFork}\n title=\"Workflow\"\n description=\"Build reusable automation workflows that users can import and customize.\"\n bullets={[\n \"Manifest + workflow JSON\",\n \"Trigger definitions\",\n \"Versioned releases\",\n \"Publish to workflow store\",\n ]}\n selected={builderType === \"workflow\"}\n onClick={() => setBuilderType(\"workflow\")}\n />\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 flex gap-3 text-sm text-text-secondary\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <p>\n Apps use OAuth to interact with platform data. Nodes and\n Workflows are packaged files you upload after creation.\n </p>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // ── Step 2: form ────────────────────────────────────────────────────────────\n const typeLabel =\n builderType === \"app\"\n ? \"App\"\n : builderType === \"node\"\n ? \"Node\"\n : \"Workflow\";\n const typeIcon =\n builderType === \"app\" ? (\n <Box size={16} />\n ) : builderType === \"node\" ? (\n <Puzzle size={16} />\n ) : (\n <GitFork size={16} />\n );\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title={`Create ${typeLabel}`}\n description={\n builderType === \"app\"\n ? \"Register a new app integration with OAuth permissions\"\n : builderType === \"node\"\n ? \"Register a new FluidGrids node package\"\n : \"Register a new reusable automation workflow\"\n }\n purpose=\"Give your integration a name, description, and the permissions it will request. Once created you will get credentials and can configure webhooks, then publish it to the marketplace when it is ready for users.\"\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Type badge + back to picker */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setBuilderType(null)}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft size={14} />\n Change type\n </button>\n <span className=\"flex items-center gap-1.5 rounded-full border border-border-subtle bg-bg-sunken px-3 py-1 text-xs font-medium\">\n {typeIcon}\n {typeLabel}\n </span>\n </div>\n\n {/* Basic info */}\n <PageSection\n title=\"Basic Information\"\n description=\"Core details about your application\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Name\"\n value={formData.name}\n onChange={handleNameChange}\n placeholder={\n builderType === \"app\"\n ? \"My Integration\"\n : builderType === \"node\"\n ? \"HTTP Request\"\n : \"Onboarding Flow\"\n }\n required\n error={errors.name}\n />\n <InputField\n label=\"Slug\"\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder=\"my-integration\"\n required\n error={errors.slug}\n hint=\"URL-friendly identifier (auto-generated from name)\"\n />\n <TextareaField\n label=\"Short Description\"\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder=\"Briefly describe what this does\"\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 characters`}\n rows={2}\n />\n <TextareaField\n label=\"Full Description\"\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder=\"Detailed description, features, and use cases...\"\n rows={5}\n hint=\"Markdown supported\"\n />\n </div>\n </PageSection>\n\n {/* Visibility + org */}\n <PageSection\n title=\"Publish Settings\"\n description=\"Control who can see this and which organization it belongs to\"\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label=\"Category\"\n value={formData.category}\n onChange={(e) => handleChange(\"category\", e.target.value)}\n options={CATEGORY_OPTIONS}\n />\n <SelectField\n label=\"Visibility\"\n value={formData.visibility}\n onChange={(e) => handleChange(\"visibility\", e.target.value)}\n options={VISIBILITY_OPTIONS}\n />\n <SelectField\n label=\"Developer Organization\"\n value={formData.developerOrganizationId}\n onChange={(e) =>\n handleChange(\"developerOrganizationId\", e.target.value)\n }\n options={orgOptions}\n />\n </div>\n </PageSection>\n\n {/* Links */}\n <PageSection title=\"Links\" description=\"External resources\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label=\"Website URL\"\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder=\"https://example.com\"\n />\n <InputField\n label=\"GitHub URL\"\n type=\"url\"\n value={formData.githubUrl}\n onChange={(e) => handleChange(\"githubUrl\", e.target.value)}\n placeholder=\"https://github.com/org/repo\"\n />\n <InputField\n label=\"Documentation URL\"\n type=\"url\"\n value={formData.documentationUrl}\n onChange={(e) =>\n handleChange(\"documentationUrl\", e.target.value)\n }\n placeholder=\"https://docs.example.com\"\n />\n <InputField\n label=\"Support URL\"\n type=\"url\"\n value={formData.supportUrl}\n onChange={(e) => handleChange(\"supportUrl\", e.target.value)}\n placeholder=\"https://support.example.com\"\n />\n </div>\n </PageSection>\n\n {/* Permissions — App type only */}\n {builderType === \"app\" && (\n <PageSection\n title=\"Permissions\"\n description=\"Choose what data your app can access. Users will see these permissions when authorizing your app — like GitHub Apps.\"\n >\n <PermissionsMatrix\n permissions={permissions}\n onChange={handlePermissionChange}\n />\n <div className=\"mt-3 flex gap-2 rounded-lg border border-border-subtle bg-bg-sunken/30 p-3 text-xs text-text-secondary\">\n <Info size={14} className=\"flex-shrink-0 mt-0.5\" />\n <span>\n <strong>Read &amp; Write</strong> implies Read. Select only\n what your app genuinely needs — fewer permissions build more\n user trust.\n </span>\n </div>\n </PageSection>\n )}\n\n {/* OAuth Endpoints — App type only */}\n {builderType === \"app\" && (\n <PageSection\n title=\"OAuth Endpoints\"\n description=\"Configure the OAuth 2.0 endpoints your app uses to authenticate workspaces. Workspaces will be redirected through these URLs during installation.\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Authorization URL\"\n type=\"url\"\n value={formData.oauthAuthUrl}\n onChange={(e) => handleChange(\"oauthAuthUrl\", e.target.value)}\n placeholder=\"https://yourapp.com/oauth/authorize\"\n hint=\"Where users are redirected to grant access\"\n />\n <InputField\n label=\"Token URL\"\n type=\"url\"\n value={formData.oauthTokenUrl}\n onChange={(e) =>\n handleChange(\"oauthTokenUrl\", e.target.value)\n }\n placeholder=\"https://yourapp.com/oauth/token\"\n hint=\"Endpoint to exchange auth code for access token\"\n />\n <InputField\n label=\"Revoke URL\"\n type=\"url\"\n value={formData.oauthRevokeUrl}\n onChange={(e) =>\n handleChange(\"oauthRevokeUrl\", e.target.value)\n }\n placeholder=\"https://yourapp.com/oauth/revoke\"\n hint=\"Endpoint to revoke tokens when app is uninstalled\"\n />\n </div>\n </PageSection>\n )}\n\n {/* Node/Workflow file upload note */}\n {builderType !== \"app\" && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 flex gap-3 text-sm text-text-secondary\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"font-medium text-text-primary\">File uploads</p>\n <p className=\"mt-1\">\n {builderType === \"node\"\n ? \"After creating your Node, go to the Versions tab to upload your ZIP bundle and manifest.json.\"\n : \"After creating your Workflow, go to the Versions tab to upload your manifest and workflow JSON definition.\"}\n </p>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={() => handleNav(\"/apps\")}\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\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\"\n >\n {isSubmitting ? (\n <>\n <Loader2 size={16} className=\"animate-spin\" />\n Creating…\n </>\n ) : (\n <>\n <Save size={16} />\n Create {typeLabel}\n </>\n )}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
@@ -101,12 +101,13 @@ var E = {
101
101
  retryEnabled: k.retryEnabled,
102
102
  maxRetries: k.retryEnabled ? k.maxRetries : 0
103
103
  }
104
- } })).data?.createWebhook;
105
- if (!e?.id) throw Error(H("devportal.createWebhook.errors.noId", "Webhook creation did not return an identifier"));
104
+ } })).data?.createWebhook, t = e?.webhook;
105
+ if (!t?.id) throw Error(H("devportal.createWebhook.errors.noId", "Webhook creation did not return an identifier"));
106
106
  V.emit("devportal.webhook.created", {
107
- webhookId: e.id,
108
- applicationId: k.applicationId
109
- }), D?.(`${O}/apps/${k.applicationId}/webhooks/${e.id}`);
107
+ webhookId: t.id,
108
+ applicationId: k.applicationId,
109
+ secret: e?.secret
110
+ }), D?.(`${O}/apps/${k.applicationId}/webhooks/${t.id}`);
110
111
  } catch (e) {
111
112
  P(e instanceof Error ? e.message : H("devportal.createWebhook.errors.createFailed", "Failed to create webhook"));
112
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CreateWebhookPage.js","names":[],"sources":["../../../src/devportal/pages/CreateWebhookPage.tsx"],"sourcesContent":["import type { FC, FormEvent } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { Loader2, Webhook } from \"lucide-react\";\nimport { ShowMore } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n CheckboxField,\n} from \"../components/FormField\";\nimport { Alert } from \"../components/ConfirmDialog\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalEventBus } from \"../events\";\nimport { WEBHOOK_EVENTS } from \"../types\";\nimport {\n type GetApplicationsQuery,\n getErrorMessage,\n useCreateWebhookMutation,\n useGetApplicationQuery,\n useGetApplicationsQuery,\n useDevportalPermission,\n} from \"../hooks\";\nimport { useTr } from \"../utils/i18n\";\n\nconst eventCategories = {\n Application: WEBHOOK_EVENTS.filter((event) =>\n event.startsWith(\"application.\"),\n ),\n Installation: WEBHOOK_EVENTS.filter((event) =>\n event.startsWith(\"installation.\"),\n ),\n User: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"user.\")),\n Workspace: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"workspace.\")),\n Project: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"project.\")),\n Task: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"task.\")),\n Comment: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"comment.\")),\n File: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"file.\")),\n Member: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"member.\")),\n};\n\ninterface CreateWebhookFormState {\n applicationId: string;\n name: string;\n description: string;\n url: string;\n events: string[];\n retryEnabled: boolean;\n maxRetries: number;\n}\n\ntype ApplicationOption =\n GetApplicationsQuery[\"applications\"][\"edges\"][number][\"node\"];\n\nexport const CreateWebhookPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath } = useDevportal();\n const [formData, setFormData] = useState<CreateWebhookFormState>({\n applicationId: appId || \"\",\n name: \"\",\n description: \"\",\n url: \"\",\n events: [],\n retryEnabled: true,\n maxRetries: 3,\n });\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [submitError, setSubmitError] = useState<string | null>(null);\n\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 } = useGetApplicationsQuery({\n variables: { limit: 100 },\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n const [createWebhook, { loading: isSubmitting }] = useCreateWebhookMutation();\n const bus = useDevportalEventBus();\n const tr = useTr();\n const createPerm = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE,\n );\n\n const applications = useMemo(\n () =>\n appsData?.applications?.edges\n ?.map(\n (edge: GetApplicationsQuery[\"applications\"][\"edges\"][number]) =>\n edge.node,\n )\n .map((application: ApplicationOption) => ({\n id: application.id,\n name: application.name,\n slug: application.slug,\n })) ?? [],\n [appsData?.applications?.edges],\n );\n\n const selectedApplication = applications.find(\n (application: { id: string; name: string; slug: string }) =>\n application.id === formData.applicationId,\n );\n\n const handleChange = <K extends keyof CreateWebhookFormState>(\n key: K,\n value: CreateWebhookFormState[K],\n ) => {\n setFormData((previous) => ({\n ...previous,\n [key]: value,\n }));\n setErrors((previous) => {\n if (!previous[key as string]) return previous;\n const nextErrors = { ...previous };\n delete nextErrors[key as string];\n return nextErrors;\n });\n };\n\n const toggleEvent = (event: string) => {\n setFormData((previous) => ({\n ...previous,\n events: previous.events.includes(event)\n ? previous.events.filter((value) => value !== event)\n : [...previous.events, event],\n }));\n };\n\n const toggleCategory = (events: readonly string[]) => {\n const allSelected = events.every((event) =>\n formData.events.includes(event),\n );\n\n setFormData((previous) => ({\n ...previous,\n events: allSelected\n ? previous.events.filter((event) => !events.includes(event))\n : [...new Set([...previous.events, ...events])],\n }));\n };\n\n const validate = (): boolean => {\n const nextErrors: Record<string, string> = {};\n\n if (!formData.applicationId) {\n nextErrors.applicationId = tr(\n \"devportal.createWebhook.errors.appRequired\",\n \"Select an application\",\n );\n }\n if (!formData.name.trim()) {\n nextErrors.name = tr(\n \"devportal.createWebhook.errors.nameRequired\",\n \"Name is required\",\n );\n }\n if (!formData.url.trim()) {\n nextErrors.url = tr(\n \"devportal.createWebhook.errors.urlRequired\",\n \"Endpoint URL is required\",\n );\n } else if (!/^https:\\/\\//i.test(formData.url)) {\n nextErrors.url = tr(\n \"devportal.createWebhook.errors.urlHttps\",\n \"Endpoint URL must use HTTPS\",\n );\n }\n if (formData.events.length === 0) {\n nextErrors.events = tr(\n \"devportal.createWebhook.errors.eventsRequired\",\n \"Select at least one event\",\n );\n }\n if (\n formData.retryEnabled &&\n (formData.maxRetries < 1 || formData.maxRetries > 10)\n ) {\n nextErrors.maxRetries = tr(\n \"devportal.createWebhook.errors.retriesRange\",\n \"Retry count must be between 1 and 10\",\n );\n }\n\n setErrors(nextErrors);\n return Object.keys(nextErrors).length === 0;\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSubmitError(null);\n\n if (!validate()) return;\n\n try {\n const result = await createWebhook({\n variables: {\n applicationId: formData.applicationId,\n input: {\n name: formData.name.trim(),\n description: formData.description.trim() || undefined,\n url: formData.url.trim(),\n events: formData.events,\n retryEnabled: formData.retryEnabled,\n maxRetries: formData.retryEnabled ? formData.maxRetries : 0,\n },\n },\n });\n\n const createdWebhook = result.data?.createWebhook;\n if (!createdWebhook?.id) {\n throw new Error(\n tr(\n \"devportal.createWebhook.errors.noId\",\n \"Webhook creation did not return an identifier\",\n ),\n );\n }\n\n bus.emit(\"devportal.webhook.created\", {\n webhookId: createdWebhook.id,\n applicationId: formData.applicationId,\n });\n\n navigate?.(\n `${basePath}/apps/${formData.applicationId}/webhooks/${createdWebhook.id}`,\n );\n } catch (error) {\n setSubmitError(\n error instanceof Error\n ? error.message\n : tr(\n \"devportal.createWebhook.errors.createFailed\",\n \"Failed to create webhook\",\n ),\n );\n }\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE}>\n <PageLayout\n title={tr(\"devportal.createWebhook.title\", \"Create Webhook\")}\n description={tr(\n \"devportal.createWebhook.description\",\n \"Set up a real webhook endpoint to receive platform events through the public gateway\",\n )}\n purpose={tr(\n \"devportal.createWebhook.purpose\",\n \"Point a webhook at a URL you host so the platform pushes events (installs, payments, app updates) to your backend in real time. Choose which events to subscribe to; you will get a signing secret to verify that deliveries genuinely came from us.\",\n )}\n backLink={\n formData.applicationId\n ? `/apps/${formData.applicationId}/webhooks`\n : \"/webhooks\"\n }\n backLabel={tr(\"devportal.createWebhook.back\", \"Back to Webhooks\")}\n >\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <Alert variant=\"info\">\n {tr(\n \"devportal.createWebhook.info\",\n \"Webhook deliveries are signed and sent through the public global gateway. Use an HTTPS endpoint that accepts POST requests and returns a 2xx response for successful deliveries.\",\n )}\n </Alert>\n\n {submitError && <Alert variant=\"error\">{submitError}</Alert>}\n {appsError && !appId && (\n <Alert variant=\"error\">{getErrorMessage(appsError)}</Alert>\n )}\n\n <PageSection\n title={tr(\"devportal.createWebhook.basicInfo\", \"Basic Information\")}\n >\n <div className=\"space-y-4 rounded-card border border-border-seam bg-bg-surface p-4\">\n {!appId && (\n <label className=\"grid gap-2 text-sm font-medium\">\n <span>\n {tr(\"devportal.createWebhook.application\", \"Application\")}\n </span>\n <select\n value={formData.applicationId}\n onChange={(changeEvent) =>\n handleChange(\"applicationId\", changeEvent.target.value)\n }\n disabled={appsLoading}\n aria-label={tr(\n \"devportal.createWebhook.application\",\n \"Application\",\n )}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">\n {tr(\n \"devportal.createWebhook.errors.appRequired\",\n \"Select an application\",\n )}\n </option>\n {applications.map(\n (application: { id: string; name: string }) => (\n <option key={application.id} value={application.id}>\n {application.name}\n </option>\n ),\n )}\n </select>\n {errors.applicationId && (\n <span className=\"text-sm text-status-error-text\">\n {errors.applicationId}\n </span>\n )}\n </label>\n )}\n\n {appsLoading && !appId && (\n <div\n className=\"flex items-center gap-2 text-sm text-text-secondary\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2\n className=\"h-4 w-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\n \"devportal.createWebhook.loadingApps\",\n \"Loading applications...\",\n )}\n </div>\n )}\n\n {selectedApplication && (\n <div className=\"rounded-md border bg-bg-sunken/40 px-3 py-2 text-sm text-text-secondary\">\n {tr(\n \"devportal.createWebhook.creatingFor\",\n \"Creating webhook for\",\n )}{\" \"}\n <span className=\"font-medium text-text-primary\">\n {selectedApplication.name}\n </span>\n </div>\n )}\n\n <InputField\n label={tr(\n \"devportal.createWebhook.webhookName\",\n \"Webhook Name\",\n )}\n value={formData.name}\n onChange={(changeEvent) =>\n handleChange(\"name\", changeEvent.target.value)\n }\n placeholder={tr(\n \"devportal.createWebhook.webhookNamePlaceholder\",\n \"Production webhook\",\n )}\n error={errors.name}\n required\n />\n\n <TextareaField\n label={tr(\n \"devportal.createWebhook.descriptionLabel\",\n \"Description\",\n )}\n value={formData.description}\n onChange={(changeEvent) =>\n handleChange(\"description\", changeEvent.target.value)\n }\n placeholder={tr(\n \"devportal.createWebhook.descriptionPlaceholder\",\n \"What this webhook is used for\",\n )}\n rows={2}\n />\n\n <InputField\n label={tr(\n \"devportal.createWebhook.endpointUrl\",\n \"Endpoint URL\",\n )}\n type=\"url\"\n value={formData.url}\n onChange={(changeEvent) =>\n handleChange(\"url\", changeEvent.target.value)\n }\n placeholder=\"https://your-app.example.com/webhooks/burdenoff\"\n hint={tr(\"devportal.createWebhook.httpsOnly\", \"HTTPS only\")}\n error={errors.url}\n required\n />\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\n \"devportal.createWebhook.subscribedEvents\",\n \"Subscribed Events\",\n )}\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n {errors.events && (\n <p className=\"mb-3 text-sm text-status-error-text\" role=\"alert\">\n {errors.events}\n </p>\n )}\n <ShowMore\n collapsedHeight={320}\n label={tr(\n \"devportal.createWebhook.showAllEvents\",\n \"Show all event categories\",\n )}\n >\n <div className=\"space-y-4\">\n {Object.entries(eventCategories).map(([category, events]) => (\n <section\n key={category}\n className=\"space-y-3 border-b border-border-seam pb-4 last:border-b-0 last:pb-0\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <h3 className=\"font-medium\">\n {tr(\n `devportal.createWebhook.category.${category.toLowerCase()}`,\n category,\n )}\n </h3>\n <button\n type=\"button\"\n onClick={() => toggleCategory(events)}\n className=\"text-xs font-medium text-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {events.every((item) => formData.events.includes(item))\n ? tr(\n \"devportal.createWebhook.deselectAll\",\n \"Deselect all\",\n )\n : tr(\"devportal.createWebhook.selectAll\", \"Select all\")}\n </button>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {events.map((eventName) => (\n <label\n key={eventName}\n className=\"flex items-center gap-2 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm\"\n >\n <input\n type=\"checkbox\"\n checked={formData.events.includes(eventName)}\n onChange={() => toggleEvent(eventName)}\n className=\"rounded border-border-default text-primary focus:ring-primary\"\n aria-label={eventName}\n />\n <code className=\"text-xs text-text-primary\">\n {eventName}\n </code>\n </label>\n ))}\n </div>\n </section>\n ))}\n </div>\n </ShowMore>\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\n \"devportal.createWebhook.retrySettings\",\n \"Retry Settings\",\n )}\n >\n <div className=\"space-y-4 rounded-card border border-border-seam bg-bg-surface p-4\">\n <CheckboxField\n label={tr(\n \"devportal.createWebhook.enableRetries\",\n \"Enable retries\",\n )}\n description={tr(\n \"devportal.createWebhook.enableRetriesDesc\",\n \"Retry failed deliveries automatically\",\n )}\n checked={formData.retryEnabled}\n onChange={(changeEvent) =>\n handleChange(\"retryEnabled\", changeEvent.target.checked)\n }\n />\n\n {formData.retryEnabled && (\n <InputField\n label={tr(\n \"devportal.createWebhook.maxRetries\",\n \"Maximum retries\",\n )}\n type=\"number\"\n value={formData.maxRetries}\n onChange={(changeEvent) =>\n handleChange(\"maxRetries\", Number(changeEvent.target.value))\n }\n min={1}\n max={10}\n hint={tr(\n \"devportal.createWebhook.attemptsHint\",\n \"1 to 10 attempts\",\n )}\n error={errors.maxRetries}\n />\n )}\n </div>\n </PageSection>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <button\n type=\"button\"\n onClick={() =>\n navigate?.(\n `${basePath}${formData.applicationId ? `/apps/${formData.applicationId}/webhooks` : \"/webhooks\"}`,\n )\n }\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\"devportal.common.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || appsLoading || !createPerm.allowed}\n aria-disabled={isSubmitting || appsLoading || !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 justify-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 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {isSubmitting ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Webhook size={16} aria-hidden=\"true\" />\n )}\n {isSubmitting\n ? tr(\"devportal.createWebhook.creating\", \"Creating...\")\n : tr(\"devportal.createWebhook.title\", \"Create Webhook\")}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreateWebhookPage.js","names":[],"sources":["../../../src/devportal/pages/CreateWebhookPage.tsx"],"sourcesContent":["import type { FC, FormEvent } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { Loader2, Webhook } from \"lucide-react\";\nimport { ShowMore } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n CheckboxField,\n} from \"../components/FormField\";\nimport { Alert } from \"../components/ConfirmDialog\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalEventBus } from \"../events\";\nimport { WEBHOOK_EVENTS } from \"../types\";\nimport {\n type GetApplicationsQuery,\n getErrorMessage,\n useCreateWebhookMutation,\n useGetApplicationQuery,\n useGetApplicationsQuery,\n useDevportalPermission,\n} from \"../hooks\";\nimport { useTr } from \"../utils/i18n\";\n\nconst eventCategories = {\n Application: WEBHOOK_EVENTS.filter((event) =>\n event.startsWith(\"application.\"),\n ),\n Installation: WEBHOOK_EVENTS.filter((event) =>\n event.startsWith(\"installation.\"),\n ),\n User: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"user.\")),\n Workspace: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"workspace.\")),\n Project: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"project.\")),\n Task: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"task.\")),\n Comment: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"comment.\")),\n File: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"file.\")),\n Member: WEBHOOK_EVENTS.filter((event) => event.startsWith(\"member.\")),\n};\n\ninterface CreateWebhookFormState {\n applicationId: string;\n name: string;\n description: string;\n url: string;\n events: string[];\n retryEnabled: boolean;\n maxRetries: number;\n}\n\ntype ApplicationOption =\n GetApplicationsQuery[\"applications\"][\"edges\"][number][\"node\"];\n\nexport const CreateWebhookPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath } = useDevportal();\n const [formData, setFormData] = useState<CreateWebhookFormState>({\n applicationId: appId || \"\",\n name: \"\",\n description: \"\",\n url: \"\",\n events: [],\n retryEnabled: true,\n maxRetries: 3,\n });\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [submitError, setSubmitError] = useState<string | null>(null);\n\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 } = useGetApplicationsQuery({\n variables: { limit: 100 },\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n const [createWebhook, { loading: isSubmitting }] = useCreateWebhookMutation();\n const bus = useDevportalEventBus();\n const tr = useTr();\n const createPerm = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE,\n );\n\n const applications = useMemo(\n () =>\n appsData?.applications?.edges\n ?.map(\n (edge: GetApplicationsQuery[\"applications\"][\"edges\"][number]) =>\n edge.node,\n )\n .map((application: ApplicationOption) => ({\n id: application.id,\n name: application.name,\n slug: application.slug,\n })) ?? [],\n [appsData?.applications?.edges],\n );\n\n const selectedApplication = applications.find(\n (application: { id: string; name: string; slug: string }) =>\n application.id === formData.applicationId,\n );\n\n const handleChange = <K extends keyof CreateWebhookFormState>(\n key: K,\n value: CreateWebhookFormState[K],\n ) => {\n setFormData((previous) => ({\n ...previous,\n [key]: value,\n }));\n setErrors((previous) => {\n if (!previous[key as string]) return previous;\n const nextErrors = { ...previous };\n delete nextErrors[key as string];\n return nextErrors;\n });\n };\n\n const toggleEvent = (event: string) => {\n setFormData((previous) => ({\n ...previous,\n events: previous.events.includes(event)\n ? previous.events.filter((value) => value !== event)\n : [...previous.events, event],\n }));\n };\n\n const toggleCategory = (events: readonly string[]) => {\n const allSelected = events.every((event) =>\n formData.events.includes(event),\n );\n\n setFormData((previous) => ({\n ...previous,\n events: allSelected\n ? previous.events.filter((event) => !events.includes(event))\n : [...new Set([...previous.events, ...events])],\n }));\n };\n\n const validate = (): boolean => {\n const nextErrors: Record<string, string> = {};\n\n if (!formData.applicationId) {\n nextErrors.applicationId = tr(\n \"devportal.createWebhook.errors.appRequired\",\n \"Select an application\",\n );\n }\n if (!formData.name.trim()) {\n nextErrors.name = tr(\n \"devportal.createWebhook.errors.nameRequired\",\n \"Name is required\",\n );\n }\n if (!formData.url.trim()) {\n nextErrors.url = tr(\n \"devportal.createWebhook.errors.urlRequired\",\n \"Endpoint URL is required\",\n );\n } else if (!/^https:\\/\\//i.test(formData.url)) {\n nextErrors.url = tr(\n \"devportal.createWebhook.errors.urlHttps\",\n \"Endpoint URL must use HTTPS\",\n );\n }\n if (formData.events.length === 0) {\n nextErrors.events = tr(\n \"devportal.createWebhook.errors.eventsRequired\",\n \"Select at least one event\",\n );\n }\n if (\n formData.retryEnabled &&\n (formData.maxRetries < 1 || formData.maxRetries > 10)\n ) {\n nextErrors.maxRetries = tr(\n \"devportal.createWebhook.errors.retriesRange\",\n \"Retry count must be between 1 and 10\",\n );\n }\n\n setErrors(nextErrors);\n return Object.keys(nextErrors).length === 0;\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSubmitError(null);\n\n if (!validate()) return;\n\n try {\n const result = await createWebhook({\n variables: {\n applicationId: formData.applicationId,\n input: {\n name: formData.name.trim(),\n description: formData.description.trim() || undefined,\n url: formData.url.trim(),\n events: formData.events,\n retryEnabled: formData.retryEnabled,\n maxRetries: formData.retryEnabled ? formData.maxRetries : 0,\n },\n },\n });\n\n const createResult = result.data?.createWebhook;\n const createdWebhook = createResult?.webhook;\n if (!createdWebhook?.id) {\n throw new Error(\n tr(\n \"devportal.createWebhook.errors.noId\",\n \"Webhook creation did not return an identifier\",\n ),\n );\n }\n\n bus.emit(\"devportal.webhook.created\", {\n webhookId: createdWebhook.id,\n applicationId: formData.applicationId,\n secret: createResult?.secret,\n });\n\n navigate?.(\n `${basePath}/apps/${formData.applicationId}/webhooks/${createdWebhook.id}`,\n );\n } catch (error) {\n setSubmitError(\n error instanceof Error\n ? error.message\n : tr(\n \"devportal.createWebhook.errors.createFailed\",\n \"Failed to create webhook\",\n ),\n );\n }\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE}>\n <PageLayout\n title={tr(\"devportal.createWebhook.title\", \"Create Webhook\")}\n description={tr(\n \"devportal.createWebhook.description\",\n \"Set up a real webhook endpoint to receive platform events through the public gateway\",\n )}\n purpose={tr(\n \"devportal.createWebhook.purpose\",\n \"Point a webhook at a URL you host so the platform pushes events (installs, payments, app updates) to your backend in real time. Choose which events to subscribe to; you will get a signing secret to verify that deliveries genuinely came from us.\",\n )}\n backLink={\n formData.applicationId\n ? `/apps/${formData.applicationId}/webhooks`\n : \"/webhooks\"\n }\n backLabel={tr(\"devportal.createWebhook.back\", \"Back to Webhooks\")}\n >\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <Alert variant=\"info\">\n {tr(\n \"devportal.createWebhook.info\",\n \"Webhook deliveries are signed and sent through the public global gateway. Use an HTTPS endpoint that accepts POST requests and returns a 2xx response for successful deliveries.\",\n )}\n </Alert>\n\n {submitError && <Alert variant=\"error\">{submitError}</Alert>}\n {appsError && !appId && (\n <Alert variant=\"error\">{getErrorMessage(appsError)}</Alert>\n )}\n\n <PageSection\n title={tr(\"devportal.createWebhook.basicInfo\", \"Basic Information\")}\n >\n <div className=\"space-y-4 rounded-card border border-border-seam bg-bg-surface p-4\">\n {!appId && (\n <label className=\"grid gap-2 text-sm font-medium\">\n <span>\n {tr(\"devportal.createWebhook.application\", \"Application\")}\n </span>\n <select\n value={formData.applicationId}\n onChange={(changeEvent) =>\n handleChange(\"applicationId\", changeEvent.target.value)\n }\n disabled={appsLoading}\n aria-label={tr(\n \"devportal.createWebhook.application\",\n \"Application\",\n )}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">\n {tr(\n \"devportal.createWebhook.errors.appRequired\",\n \"Select an application\",\n )}\n </option>\n {applications.map(\n (application: { id: string; name: string }) => (\n <option key={application.id} value={application.id}>\n {application.name}\n </option>\n ),\n )}\n </select>\n {errors.applicationId && (\n <span className=\"text-sm text-status-error-text\">\n {errors.applicationId}\n </span>\n )}\n </label>\n )}\n\n {appsLoading && !appId && (\n <div\n className=\"flex items-center gap-2 text-sm text-text-secondary\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2\n className=\"h-4 w-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\n \"devportal.createWebhook.loadingApps\",\n \"Loading applications...\",\n )}\n </div>\n )}\n\n {selectedApplication && (\n <div className=\"rounded-md border bg-bg-sunken/40 px-3 py-2 text-sm text-text-secondary\">\n {tr(\n \"devportal.createWebhook.creatingFor\",\n \"Creating webhook for\",\n )}{\" \"}\n <span className=\"font-medium text-text-primary\">\n {selectedApplication.name}\n </span>\n </div>\n )}\n\n <InputField\n label={tr(\n \"devportal.createWebhook.webhookName\",\n \"Webhook Name\",\n )}\n value={formData.name}\n onChange={(changeEvent) =>\n handleChange(\"name\", changeEvent.target.value)\n }\n placeholder={tr(\n \"devportal.createWebhook.webhookNamePlaceholder\",\n \"Production webhook\",\n )}\n error={errors.name}\n required\n />\n\n <TextareaField\n label={tr(\n \"devportal.createWebhook.descriptionLabel\",\n \"Description\",\n )}\n value={formData.description}\n onChange={(changeEvent) =>\n handleChange(\"description\", changeEvent.target.value)\n }\n placeholder={tr(\n \"devportal.createWebhook.descriptionPlaceholder\",\n \"What this webhook is used for\",\n )}\n rows={2}\n />\n\n <InputField\n label={tr(\n \"devportal.createWebhook.endpointUrl\",\n \"Endpoint URL\",\n )}\n type=\"url\"\n value={formData.url}\n onChange={(changeEvent) =>\n handleChange(\"url\", changeEvent.target.value)\n }\n placeholder=\"https://your-app.example.com/webhooks/burdenoff\"\n hint={tr(\"devportal.createWebhook.httpsOnly\", \"HTTPS only\")}\n error={errors.url}\n required\n />\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\n \"devportal.createWebhook.subscribedEvents\",\n \"Subscribed Events\",\n )}\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n {errors.events && (\n <p className=\"mb-3 text-sm text-status-error-text\" role=\"alert\">\n {errors.events}\n </p>\n )}\n <ShowMore\n collapsedHeight={320}\n label={tr(\n \"devportal.createWebhook.showAllEvents\",\n \"Show all event categories\",\n )}\n >\n <div className=\"space-y-4\">\n {Object.entries(eventCategories).map(([category, events]) => (\n <section\n key={category}\n className=\"space-y-3 border-b border-border-seam pb-4 last:border-b-0 last:pb-0\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <h3 className=\"font-medium\">\n {tr(\n `devportal.createWebhook.category.${category.toLowerCase()}`,\n category,\n )}\n </h3>\n <button\n type=\"button\"\n onClick={() => toggleCategory(events)}\n className=\"text-xs font-medium text-primary hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {events.every((item) =>\n formData.events.includes(item),\n )\n ? tr(\n \"devportal.createWebhook.deselectAll\",\n \"Deselect all\",\n )\n : tr(\n \"devportal.createWebhook.selectAll\",\n \"Select all\",\n )}\n </button>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {events.map((eventName) => (\n <label\n key={eventName}\n className=\"flex items-center gap-2 rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm\"\n >\n <input\n type=\"checkbox\"\n checked={formData.events.includes(eventName)}\n onChange={() => toggleEvent(eventName)}\n className=\"rounded border-border-default text-primary focus:ring-primary\"\n aria-label={eventName}\n />\n <code className=\"text-xs text-text-primary\">\n {eventName}\n </code>\n </label>\n ))}\n </div>\n </section>\n ))}\n </div>\n </ShowMore>\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\n \"devportal.createWebhook.retrySettings\",\n \"Retry Settings\",\n )}\n >\n <div className=\"space-y-4 rounded-card border border-border-seam bg-bg-surface p-4\">\n <CheckboxField\n label={tr(\n \"devportal.createWebhook.enableRetries\",\n \"Enable retries\",\n )}\n description={tr(\n \"devportal.createWebhook.enableRetriesDesc\",\n \"Retry failed deliveries automatically\",\n )}\n checked={formData.retryEnabled}\n onChange={(changeEvent) =>\n handleChange(\"retryEnabled\", changeEvent.target.checked)\n }\n />\n\n {formData.retryEnabled && (\n <InputField\n label={tr(\n \"devportal.createWebhook.maxRetries\",\n \"Maximum retries\",\n )}\n type=\"number\"\n value={formData.maxRetries}\n onChange={(changeEvent) =>\n handleChange(\"maxRetries\", Number(changeEvent.target.value))\n }\n min={1}\n max={10}\n hint={tr(\n \"devportal.createWebhook.attemptsHint\",\n \"1 to 10 attempts\",\n )}\n error={errors.maxRetries}\n />\n )}\n </div>\n </PageSection>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <button\n type=\"button\"\n onClick={() =>\n navigate?.(\n `${basePath}${formData.applicationId ? `/apps/${formData.applicationId}/webhooks` : \"/webhooks\"}`,\n )\n }\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\"devportal.common.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || appsLoading || !createPerm.allowed}\n aria-disabled={isSubmitting || appsLoading || !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 justify-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 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {isSubmitting ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Webhook size={16} aria-hidden=\"true\" />\n )}\n {isSubmitting\n ? tr(\"devportal.createWebhook.creating\", \"Creating...\")\n : tr(\"devportal.createWebhook.title\", \"Create Webhook\")}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/devportal/pages/DashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n AppWindow,\n Building2,\n Key,\n Webhook,\n TrendingUp,\n Users,\n DollarSign,\n Activity,\n Plus,\n ArrowRight,\n Loader2,\n Store,\n} from \"lucide-react\";\nimport { EmphasisPanel, GradientText, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, DeveloperOrganization } from \"../types\";\nimport {\n useGetDevportalDashboardBootstrapQuery,\n type OrganizationFieldsFragment,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Developer Portal Dashboard Page\n *\n * Shows an overview of the developer's applications, organizations,\n * and key metrics using real data from the GraphQL API.\n */\nexport const DashboardPage: FC = () => {\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\n // Fetch dashboard bootstrap in a single global query\n const { data, loading } = useGetDevportalDashboardBootstrapQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations: OrganizationFieldsFragment[] =\n data?.myDeveloperOrganizations ?? [];\n\n // First-time users: redirect to setup wizard when no org exists and they\n // haven't already dismissed the wizard in this browser.\n useEffect(() => {\n if (loading || !data) return;\n if (organizations.length > 0) return;\n try {\n if (localStorage.getItem(\"devportal_setup_dismissed\")) return;\n } catch {\n // localStorage unavailable — don't redirect\n return;\n }\n navigate?.(`${basePath}/setup`);\n }, [loading, data, organizations.length, navigate, basePath]);\n const applications: ApplicationFieldsFragment[] =\n data?.applications?.edges?.map(\n (e: { node: ApplicationFieldsFragment }) => e.node,\n ) ?? [];\n\n // Calculate aggregate stats\n const stats = {\n totalApps: data?.applications?.totalCount ?? 0,\n totalOrgs: organizations.length,\n totalApiKeys: 0,\n totalWebhooks: 0,\n totalApiCalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + parseInt(org.totalApiCalls || \"0\"),\n 0,\n ),\n activeInstalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls || 0),\n 0,\n ),\n monthlyRevenue: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalRevenue || 0),\n 0,\n ),\n errorRate: 0,\n };\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const isLoading = loading;\n\n const hasOrgs = !isLoading && organizations.length > 0;\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n heroBackdrop\n title={\n <>\n {tr(\"devportal.developer\", \"Developer\")}{\" \"}\n <GradientText>{tr(\"devportal.portal\", \"Portal\")}</GradientText>\n </>\n }\n description={tr(\n \"devportal.buildManageMonitor\",\n \"Build, manage, and monitor your applications\",\n )}\n purpose={tr(\n \"devportal.dashboardPurpose\",\n \"This is your developer home base — a single view of the apps you ship, the organizations behind them, and how they are performing (installs, API usage, revenue). Start an organization, then create and publish apps from here.\",\n )}\n actions={\n hasOrgs ? (\n <button\n data-tour=\"devportal-dashboard-create-app-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 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : !isLoading ? (\n <button\n data-tour=\"devportal-dashboard-create-org-button\"\n onClick={() => handleNavigate(\"/organizations/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 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n ) : null\n }\n >\n {/* Stats Overview — primary emphasis zone */}\n <EmphasisPanel data-tour=\"devportal-dashboard-stats-overview\">\n <StatCardGrid columns={4}>\n <StatCard\n featured\n title={tr(\"devportal.totalApps\", \"Total Apps\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalApps\n )\n }\n icon={<AppWindow size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.organizations\", \"Organizations\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalOrgs\n )\n }\n icon={<Building2 size={20} />}\n />\n <StatCard\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n value={stats.totalApiKeys}\n icon={<Key size={20} />}\n description={tr(\"devportal.activeKeys\", \"Active keys\")}\n />\n <StatCard\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n value={stats.totalWebhooks}\n icon={<Webhook size={20} />}\n description={tr(\"devportal.configured\", \"Configured\")}\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n {/* Guided next steps for new developers (dismissable) */}\n {!isLoading && (\n <NextSteps\n storageKey=\"devportal-dashboard\"\n steps={[\n {\n id: \"create-org\",\n label: tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n ),\n description: tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n ),\n icon: <Building2 size={16} />,\n onClick: () => handleNavigate(\"/organizations/new\"),\n done: hasOrgs,\n },\n {\n id: \"create-app\",\n label: tr(\"devportal.createApp\", \"Create App\"),\n description: tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n ),\n icon: <AppWindow size={16} />,\n onClick: () => handleNavigate(\"/apps/new\"),\n done: stats.totalApps > 0,\n },\n {\n id: \"api-key\",\n label: tr(\"devportal.generateApiKey\", \"Generate API Key\"),\n description: tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n ),\n icon: <Key size={16} />,\n onClick: () => handleNavigate(\"/api-keys\"),\n },\n {\n id: \"webhooks\",\n label: tr(\"devportal.configureWebhooks\", \"Configure Webhooks\"),\n description: tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n ),\n icon: <Webhook size={16} />,\n onClick: () => handleNavigate(\"/webhooks\"),\n },\n ]}\n />\n )}\n\n {/* Usage Stats */}\n <PageSection\n title={tr(\"devportal.usageOverview\", \"Usage Overview\")}\n data-tour=\"devportal-dashboard-usage-overview\"\n >\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.apiCalls\", \"API Calls\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.totalApiCalls > 1000000 ? (\n `${(stats.totalApiCalls / 1000000).toFixed(1)}M`\n ) : stats.totalApiCalls > 1000 ? (\n `${(stats.totalApiCalls / 1000).toFixed(1)}K`\n ) : (\n stats.totalApiCalls.toString()\n )\n }\n icon={<Activity size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.activeInstalls\", \"Active Installs\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.activeInstalls > 1000 ? (\n `${(stats.activeInstalls / 1000).toFixed(1)}K`\n ) : (\n stats.activeInstalls.toString()\n )\n }\n icon={<Users size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.monthlyRevenue\", \"Monthly Revenue\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n `$${stats.monthlyRevenue.toLocaleString()}`\n )\n }\n icon={<DollarSign size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.errorRate\", \"Error Rate\")}\n value={`${stats.errorRate}%`}\n icon={<TrendingUp size={20} />}\n trend={undefined}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Quick Actions & Recent Items */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Recent Apps */}\n <PageSection\n data-tour=\"devportal-dashboard-recent-apps\"\n title={tr(\"devportal.recentApplications\", \"Recent Applications\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications\n .slice(0, 3)\n .map((app: ApplicationFieldsFragment) => (\n <AppListItem\n key={app.id}\n app={app as Application}\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title={tr(\"devportal.noApplicationsYet\", \"No applications yet\")}\n description={\n hasOrgs\n ? tr(\n \"devportal.createFirstApplicationToGetStarted\",\n \"Create your first application to get started\",\n )\n : tr(\n \"devportal.createOrgFirstToPublishApps\",\n \"Create an organization first to start publishing apps\",\n )\n }\n action={\n hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : (\n <button\n onClick={() => handleNavigate(\"/organizations/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(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </button>\n )\n }\n />\n )}\n </PageSection>\n\n {/* Quick Actions */}\n <PageSection\n title={tr(\"devportal.quickActions\", \"Quick Actions\")}\n data-tour=\"devportal-dashboard-quick-actions\"\n >\n <div className=\"grid gap-3\">\n {!hasOrgs ? (\n <QuickActionCard\n title={tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n description={tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n )}\n icon={<Building2 size={20} />}\n onClick={() => handleNavigate(\"/organizations/new\")}\n primary\n />\n ) : (\n <QuickActionCard\n title={tr(\"devportal.createNewApp\", \"Create New App\")}\n description={tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n )}\n icon={<AppWindow size={20} />}\n onClick={() => handleNavigate(\"/apps/new\")}\n />\n )}\n {hasOrgs && (\n <QuickActionCard\n title={tr(\"devportal.publisherProfile\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageYourStorePresence\",\n \"Manage your store publisher presence\",\n )}\n icon={<Store size={20} />}\n onClick={() => handleNavigate(\"/publisher/profile\")}\n />\n )}\n <QuickActionCard\n title={tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n description={tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n )}\n icon={<Key size={20} />}\n onClick={() => handleNavigate(\"/api-keys\")}\n />\n <QuickActionCard\n title={tr(\"devportal.configureWebhooks\", \"Configure Webhooks\")}\n description={tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n )}\n icon={<Webhook size={20} />}\n onClick={() => handleNavigate(\"/webhooks\")}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Organizations */}\n {isLoading ? (\n <PageSection\n data-tour=\"devportal-dashboard-organizations\"\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n >\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n </PageSection>\n ) : organizations.length > 0 ? (\n <PageSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {organizations\n .slice(0, 3)\n .map((org: OrganizationFieldsFragment) => (\n <OrgCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n </PageSection>\n ) : null}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Quick action card component\nconst QuickActionCard: FC<{\n title: string;\n description: string;\n icon: React.ReactNode;\n onClick: () => void;\n primary?: boolean;\n}> = ({ title, description, icon, onClick, primary }) => (\n <button\n onClick={onClick}\n className={[\n \"flex w-full items-center gap-4 rounded-card border p-4 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n primary\n ? \"border-primary bg-action-primary-bg/5 hover:bg-action-primary-bg/10\"\n : \"border-border-seam bg-bg-surface shadow-[var(--shadow-pop)] hover:bg-accent\",\n ].join(\" \")}\n >\n <div\n className={[\n \"rounded-lg p-2\",\n primary ? \"bg-action-primary-bg/20 text-primary\" : \"bg-action-primary-bg/10 text-primary\",\n ].join(\" \")}\n >\n {icon}\n </div>\n <div>\n <p className=\"font-medium\">{title}</p>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n </div>\n </button>\n);\n\n// Organization card component\nconst OrgCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, onClick }) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n return (\n <button\n onClick={onClick}\n className=\"w-full rounded-card border border-border-seam bg-bg-surface p-4 text-left shadow-[var(--shadow-pop)] transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n >\n <div className=\"flex items-center gap-3\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={20} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{org.name}</p>\n <p className=\"text-sm text-text-secondary\">\n {org.publishedApps}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n </div>\n <div className=\"mt-3 flex gap-4 text-sm text-text-secondary\">\n <span>\n {(org.totalInstalls || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n <span>\n ${(org.totalRevenue || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.revenue\", \"revenue\")}\n </span>\n </div>\n </button>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/devportal/pages/DashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n AppWindow,\n Building2,\n Key,\n Webhook,\n TrendingUp,\n Users,\n DollarSign,\n Activity,\n Plus,\n ArrowRight,\n Loader2,\n Store,\n} from \"lucide-react\";\nimport { EmphasisPanel, GradientText, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, DeveloperOrganization } from \"../types\";\nimport {\n useGetDevportalDashboardBootstrapQuery,\n type OrganizationFieldsFragment,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Developer Portal Dashboard Page\n *\n * Shows an overview of the developer's applications, organizations,\n * and key metrics using real data from the GraphQL API.\n */\nexport const DashboardPage: FC = () => {\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\n // Fetch dashboard bootstrap in a single global query\n const { data, loading } = useGetDevportalDashboardBootstrapQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations: OrganizationFieldsFragment[] =\n data?.myDeveloperOrganizations ?? [];\n\n // First-time users: redirect to setup wizard when no org exists and they\n // haven't already dismissed the wizard in this browser.\n useEffect(() => {\n if (loading || !data) return;\n if (organizations.length > 0) return;\n try {\n if (localStorage.getItem(\"devportal_setup_dismissed\")) return;\n } catch {\n // localStorage unavailable — don't redirect\n return;\n }\n navigate?.(`${basePath}/setup`);\n }, [loading, data, organizations.length, navigate, basePath]);\n const applications: ApplicationFieldsFragment[] =\n data?.applications?.edges?.map(\n (e: { node: ApplicationFieldsFragment }) => e.node,\n ) ?? [];\n\n // Calculate aggregate stats\n const stats = {\n totalApps: data?.applications?.totalCount ?? 0,\n totalOrgs: organizations.length,\n totalApiKeys: 0,\n totalWebhooks: 0,\n totalApiCalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + parseInt(org.totalApiCalls || \"0\"),\n 0,\n ),\n activeInstalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls || 0),\n 0,\n ),\n monthlyRevenue: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalRevenue || 0),\n 0,\n ),\n errorRate: 0,\n };\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const isLoading = loading;\n\n const hasOrgs = !isLoading && organizations.length > 0;\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n heroBackdrop\n title={\n <>\n {tr(\"devportal.developer\", \"Developer\")}{\" \"}\n <GradientText>{tr(\"devportal.portal\", \"Portal\")}</GradientText>\n </>\n }\n description={tr(\n \"devportal.buildManageMonitor\",\n \"Build, manage, and monitor your applications\",\n )}\n purpose={tr(\n \"devportal.dashboardPurpose\",\n \"This is your developer home base — a single view of the apps you ship, the organizations behind them, and how they are performing (installs, API usage, revenue). Start an organization, then create and publish apps from here.\",\n )}\n actions={\n hasOrgs ? (\n <button\n data-tour=\"devportal-dashboard-create-app-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 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : !isLoading ? (\n <button\n data-tour=\"devportal-dashboard-create-org-button\"\n onClick={() => handleNavigate(\"/organizations/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 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n ) : null\n }\n >\n {/* Stats Overview — primary emphasis zone */}\n <EmphasisPanel data-tour=\"devportal-dashboard-stats-overview\">\n <StatCardGrid columns={4}>\n <StatCard\n featured\n title={tr(\"devportal.totalApps\", \"Total Apps\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalApps\n )\n }\n icon={<AppWindow size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.organizations\", \"Organizations\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalOrgs\n )\n }\n icon={<Building2 size={20} />}\n />\n <StatCard\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n value={stats.totalApiKeys}\n icon={<Key size={20} />}\n description={tr(\"devportal.activeKeys\", \"Active keys\")}\n />\n <StatCard\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n value={stats.totalWebhooks}\n icon={<Webhook size={20} />}\n description={tr(\"devportal.configured\", \"Configured\")}\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n {/* Guided next steps for new developers (dismissable) */}\n {!isLoading && (\n <NextSteps\n storageKey=\"devportal-dashboard\"\n steps={[\n {\n id: \"create-org\",\n label: tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n ),\n description: tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n ),\n icon: <Building2 size={16} />,\n onClick: () => handleNavigate(\"/organizations/new\"),\n done: hasOrgs,\n },\n {\n id: \"create-app\",\n label: tr(\"devportal.createApp\", \"Create App\"),\n description: tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n ),\n icon: <AppWindow size={16} />,\n onClick: () => handleNavigate(\"/apps/new\"),\n done: stats.totalApps > 0,\n },\n {\n id: \"api-key\",\n label: tr(\"devportal.generateApiKey\", \"Generate API Key\"),\n description: tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n ),\n icon: <Key size={16} />,\n onClick: () => handleNavigate(\"/api-keys\"),\n },\n {\n id: \"webhooks\",\n label: tr(\"devportal.configureWebhooks\", \"Configure Webhooks\"),\n description: tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n ),\n icon: <Webhook size={16} />,\n onClick: () => handleNavigate(\"/webhooks\"),\n },\n ]}\n />\n )}\n\n {/* Usage Stats */}\n <PageSection\n title={tr(\"devportal.usageOverview\", \"Usage Overview\")}\n data-tour=\"devportal-dashboard-usage-overview\"\n >\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.apiCalls\", \"API Calls\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.totalApiCalls > 1000000 ? (\n `${(stats.totalApiCalls / 1000000).toFixed(1)}M`\n ) : stats.totalApiCalls > 1000 ? (\n `${(stats.totalApiCalls / 1000).toFixed(1)}K`\n ) : (\n stats.totalApiCalls.toString()\n )\n }\n icon={<Activity size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.activeInstalls\", \"Active Installs\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.activeInstalls > 1000 ? (\n `${(stats.activeInstalls / 1000).toFixed(1)}K`\n ) : (\n stats.activeInstalls.toString()\n )\n }\n icon={<Users size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.monthlyRevenue\", \"Monthly Revenue\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n `$${stats.monthlyRevenue.toLocaleString()}`\n )\n }\n icon={<DollarSign size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.errorRate\", \"Error Rate\")}\n value={`${stats.errorRate}%`}\n icon={<TrendingUp size={20} />}\n trend={undefined}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Quick Actions & Recent Items */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Recent Apps */}\n <PageSection\n data-tour=\"devportal-dashboard-recent-apps\"\n title={tr(\"devportal.recentApplications\", \"Recent Applications\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications\n .slice(0, 3)\n .map((app: ApplicationFieldsFragment) => (\n <AppListItem\n key={app.id}\n app={app as Application}\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title={tr(\"devportal.noApplicationsYet\", \"No applications yet\")}\n description={\n hasOrgs\n ? tr(\n \"devportal.createFirstApplicationToGetStarted\",\n \"Create your first application to get started\",\n )\n : tr(\n \"devportal.createOrgFirstToPublishApps\",\n \"Create an organization first to start publishing apps\",\n )\n }\n action={\n hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : (\n <button\n onClick={() => handleNavigate(\"/organizations/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(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </button>\n )\n }\n />\n )}\n </PageSection>\n\n {/* Quick Actions */}\n <PageSection\n title={tr(\"devportal.quickActions\", \"Quick Actions\")}\n data-tour=\"devportal-dashboard-quick-actions\"\n >\n <div className=\"grid gap-3\">\n {!hasOrgs ? (\n <QuickActionCard\n title={tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n description={tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n )}\n icon={<Building2 size={20} />}\n onClick={() => handleNavigate(\"/organizations/new\")}\n primary\n />\n ) : (\n <QuickActionCard\n title={tr(\"devportal.createNewApp\", \"Create New App\")}\n description={tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n )}\n icon={<AppWindow size={20} />}\n onClick={() => handleNavigate(\"/apps/new\")}\n />\n )}\n {hasOrgs && (\n <QuickActionCard\n title={tr(\"devportal.publisherProfile\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageYourStorePresence\",\n \"Manage your store publisher presence\",\n )}\n icon={<Store size={20} />}\n onClick={() => handleNavigate(\"/publisher/profile\")}\n />\n )}\n <QuickActionCard\n title={tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n description={tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n )}\n icon={<Key size={20} />}\n onClick={() => handleNavigate(\"/api-keys\")}\n />\n <QuickActionCard\n title={tr(\"devportal.configureWebhooks\", \"Configure Webhooks\")}\n description={tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n )}\n icon={<Webhook size={20} />}\n onClick={() => handleNavigate(\"/webhooks\")}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Organizations */}\n {isLoading ? (\n <PageSection\n data-tour=\"devportal-dashboard-organizations\"\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n >\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n </PageSection>\n ) : organizations.length > 0 ? (\n <PageSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {organizations\n .slice(0, 3)\n .map((org: OrganizationFieldsFragment) => (\n <OrgCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n </PageSection>\n ) : null}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Quick action card component\nconst QuickActionCard: FC<{\n title: string;\n description: string;\n icon: React.ReactNode;\n onClick: () => void;\n primary?: boolean;\n}> = ({ title, description, icon, onClick, primary }) => (\n <button\n onClick={onClick}\n className={[\n \"flex w-full items-center gap-4 rounded-card border p-4 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n primary\n ? \"border-primary bg-action-primary-bg/5 hover:bg-action-primary-bg/10\"\n : \"border-border-seam bg-bg-surface shadow-[var(--shadow-pop)] hover:bg-accent\",\n ].join(\" \")}\n >\n <div\n className={[\n \"rounded-lg p-2\",\n primary\n ? \"bg-action-primary-bg/20 text-primary\"\n : \"bg-action-primary-bg/10 text-primary\",\n ].join(\" \")}\n >\n {icon}\n </div>\n <div>\n <p className=\"font-medium\">{title}</p>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n </div>\n </button>\n);\n\n// Organization card component\nconst OrgCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, onClick }) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n return (\n <button\n onClick={onClick}\n className=\"w-full rounded-card border border-border-seam bg-bg-surface p-4 text-left shadow-[var(--shadow-pop)] transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n >\n <div className=\"flex items-center gap-3\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={20} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{org.name}</p>\n <p className=\"text-sm text-text-secondary\">\n {org.publishedApps}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n </div>\n <div className=\"mt-3 flex gap-4 text-sm text-text-secondary\">\n <span>\n {(org.totalInstalls || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n <span>\n ${(org.totalRevenue || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.revenue\", \"revenue\")}\n </span>\n </div>\n </button>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { useDevportal as e } from "../providers/DevportalProvider.js";
2
+ import { useCompleteAppOAuthMutation as t } from "../../generated/global-operations.js";
3
+ import { useEffect as n, useState as r } from "react";
4
+ import { CheckCircle as i, Loader2 as a, XCircle as o } from "lucide-react";
5
+ import { useSearchParams as s } from "react-router-dom";
6
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
+ //#region src/devportal/pages/OAuthCallbackPage.tsx
8
+ var u = () => {
9
+ let [u] = s(), { navigate: d, basePath: f } = e(), [p, m] = r("loading"), [h, g] = r(""), [_] = t();
10
+ return n(() => {
11
+ let e = u.get("code"), t = u.get("state"), n = localStorage.getItem("oauth_pending");
12
+ if (!n || !e || !t) {
13
+ m("error"), g("Missing OAuth parameters.");
14
+ return;
15
+ }
16
+ let r;
17
+ try {
18
+ r = JSON.parse(n);
19
+ } catch {
20
+ m("error"), g("Invalid OAuth session data.");
21
+ return;
22
+ }
23
+ let { installationId: i, redirectUri: a } = r;
24
+ _({ variables: {
25
+ installationId: i,
26
+ code: e,
27
+ state: t,
28
+ redirectUri: a
29
+ } }).then(() => {
30
+ localStorage.removeItem("oauth_pending"), m("success"), setTimeout(() => d?.(`${f ?? ""}/apps`), 1500);
31
+ }).catch((e) => {
32
+ m("error"), g(e.message);
33
+ });
34
+ }, []), p === "loading" ? /* @__PURE__ */ l("div", {
35
+ className: "flex flex-col items-center justify-center min-h-[60vh] gap-4",
36
+ children: [/* @__PURE__ */ c(a, { className: "h-8 w-8 animate-spin text-primary" }), /* @__PURE__ */ c("p", {
37
+ className: "text-sm text-muted-foreground",
38
+ children: "Completing authorization…"
39
+ })]
40
+ }) : p === "success" ? /* @__PURE__ */ l("div", {
41
+ className: "flex flex-col items-center justify-center min-h-[60vh] gap-4",
42
+ children: [/* @__PURE__ */ c(i, { className: "h-8 w-8 text-status-success-text" }), /* @__PURE__ */ c("p", {
43
+ className: "text-sm font-medium",
44
+ children: "Authorization successful! Redirecting…"
45
+ })]
46
+ }) : /* @__PURE__ */ l("div", {
47
+ className: "flex flex-col items-center justify-center min-h-[60vh] gap-4",
48
+ children: [
49
+ /* @__PURE__ */ c(o, { className: "h-8 w-8 text-status-error-text" }),
50
+ /* @__PURE__ */ c("p", {
51
+ className: "text-sm font-medium text-status-error-text",
52
+ children: "Authorization failed"
53
+ }),
54
+ h && /* @__PURE__ */ c("p", {
55
+ className: "text-xs text-muted-foreground",
56
+ children: h
57
+ })
58
+ ]
59
+ });
60
+ };
61
+ //#endregion
62
+ export { u as OAuthCallbackPage };
63
+
64
+ //# sourceMappingURL=OAuthCallbackPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthCallbackPage.js","names":[],"sources":["../../../src/devportal/pages/OAuthCallbackPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { CheckCircle, XCircle, Loader2 } from \"lucide-react\";\nimport { useCompleteAppOAuthMutation } from \"../../generated/global-operations\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\n\nexport const OAuthCallbackPage: FC = () => {\n const [searchParams] = useSearchParams();\n const { navigate, basePath } = useDevportal();\n const [status, setStatus] = useState<\"loading\" | \"success\" | \"error\">(\n \"loading\",\n );\n const [errorMsg, setErrorMsg] = useState(\"\");\n const [completeOAuth] = useCompleteAppOAuthMutation();\n\n useEffect(() => {\n const code = searchParams.get(\"code\");\n const state = searchParams.get(\"state\");\n const stored = localStorage.getItem(\"oauth_pending\");\n if (!stored || !code || !state) {\n setStatus(\"error\");\n setErrorMsg(\"Missing OAuth parameters.\");\n return;\n }\n let parsed: { installationId: string; redirectUri: string };\n try {\n parsed = JSON.parse(stored) as {\n installationId: string;\n redirectUri: string;\n };\n } catch {\n setStatus(\"error\");\n setErrorMsg(\"Invalid OAuth session data.\");\n return;\n }\n const { installationId, redirectUri } = parsed;\n completeOAuth({ variables: { installationId, code, state, redirectUri } })\n .then(() => {\n localStorage.removeItem(\"oauth_pending\");\n setStatus(\"success\");\n setTimeout(() => navigate?.(`${basePath ?? \"\"}/apps`), 1500);\n })\n .catch((err: Error) => {\n setStatus(\"error\");\n setErrorMsg(err.message);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n if (status === \"loading\") {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[60vh] gap-4\">\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n <p className=\"text-sm text-muted-foreground\">\n Completing authorization…\n </p>\n </div>\n );\n }\n\n if (status === \"success\") {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[60vh] gap-4\">\n <CheckCircle className=\"h-8 w-8 text-status-success-text\" />\n <p className=\"text-sm font-medium\">\n Authorization successful! Redirecting…\n </p>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[60vh] gap-4\">\n <XCircle className=\"h-8 w-8 text-status-error-text\" />\n <p className=\"text-sm font-medium text-status-error-text\">\n Authorization failed\n </p>\n {errorMsg && <p className=\"text-xs text-muted-foreground\">{errorMsg}</p>}\n </div>\n );\n};\n"],"mappings":";;;;;;;AAOA,IAAa,UAA8B;CACzC,IAAM,CAAC,KAAgB,GAAiB,EAClC,EAAE,aAAU,gBAAa,GAAc,EACvC,CAAC,GAAQ,KAAa,EAC1B,UACD,EACK,CAAC,GAAU,KAAe,EAAS,GAAG,EACtC,CAAC,KAAiB,GAA6B;AA0DrD,QAxDA,QAAgB;EACd,IAAM,IAAO,EAAa,IAAI,OAAO,EAC/B,IAAQ,EAAa,IAAI,QAAQ,EACjC,IAAS,aAAa,QAAQ,gBAAgB;AACpD,MAAI,CAAC,KAAU,CAAC,KAAQ,CAAC,GAAO;AAE9B,GADA,EAAU,QAAQ,EAClB,EAAY,4BAA4B;AACxC;;EAEF,IAAI;AACJ,MAAI;AACF,OAAS,KAAK,MAAM,EAAO;UAIrB;AAEN,GADA,EAAU,QAAQ,EAClB,EAAY,8BAA8B;AAC1C;;EAEF,IAAM,EAAE,mBAAgB,mBAAgB;AACxC,IAAc,EAAE,WAAW;GAAE;GAAgB;GAAM;GAAO;GAAa,EAAE,CAAC,CACvE,WAAW;AAGV,GAFA,aAAa,WAAW,gBAAgB,EACxC,EAAU,UAAU,EACpB,iBAAiB,IAAW,GAAG,KAAY,GAAG,OAAO,EAAE,KAAK;IAC5D,CACD,OAAO,MAAe;AAErB,GADA,EAAU,QAAQ,EAClB,EAAY,EAAI,QAAQ;IACxB;IAEH,EAAE,CAAC,EAEF,MAAW,YAEX,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAS,WAAU,qCAAsC,CAAA,EACzD,kBAAC,KAAD;GAAG,WAAU;aAAgC;GAEzC,CAAA,CACA;MAIN,MAAW,YAEX,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA,EAC5D,kBAAC,KAAD;GAAG,WAAU;aAAsB;GAE/B,CAAA,CACA;MAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAS,WAAU,kCAAmC,CAAA;GACtD,kBAAC,KAAD;IAAG,WAAU;cAA6C;IAEtD,CAAA;GACH,KAAY,kBAAC,KAAD;IAAG,WAAU;cAAiC;IAAa,CAAA;GACpE"}