@burdenoff/microfe-devportal 2026.805.2 → 2026.828.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"CreateApplicationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/CreateApplicationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA+ahC,eAAO,MAAM,qBAAqB,EAAE,EAmjBnC,CAAC"}
1
+ {"version":3,"file":"CreateApplicationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/CreateApplicationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAybhC,eAAO,MAAM,qBAAqB,EAAE,EAsjBnC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"CreateApplicationPage.js","names":[],"sources":["../../../src/devportal/pages/CreateApplicationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useRef } from \"react\";\nimport {\n ArrowLeft,\n Box,\n Check,\n ChevronRight,\n FileCode2,\n GitFork,\n Info,\n LayoutDashboard,\n LayoutGrid,\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 { InputField, SelectField } from \"../components/FormField\";\nimport { MarkdownEditor } from \"../components/MarkdownEditor\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\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 =\n \"app\" | \"node\" | \"workflow\" | \"parser\" | \"widget\" | \"dashboard\";\n\ninterface BuilderTypeMeta {\n appType:\n \"API_INTEGRATION\" | \"NODE\" | \"WORKFLOW\" | \"PARSER\" | \"WIDGET\" | \"DASHBOARD\";\n icon: FC<{ size?: number; className?: string }>;\n label: string;\n pageDescription: string;\n purpose: string;\n namePlaceholder: string;\n cardDescription: string;\n cardBullets: string[];\n}\n\nconst BUILDER_TYPE_META: Record<BuilderType, BuilderTypeMeta> = {\n app: {\n appType: \"API_INTEGRATION\",\n icon: Box,\n label: \"App\",\n pageDescription: \"Register a new app integration with OAuth permissions\",\n purpose:\n \"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 namePlaceholder: \"My Integration\",\n cardDescription:\n \"Build integrations that connect your service to the platform using OAuth tokens.\",\n cardBullets: [\n \"GitHub-style permissions\",\n \"OAuth token flow\",\n \"Webhook events\",\n \"Publish to app store\",\n ],\n },\n node: {\n appType: \"NODE\",\n icon: Puzzle,\n label: \"Node\",\n pageDescription: \"Register a new FluidGrids node package\",\n purpose:\n \"Give your node a name and description. Once created you can upload your ZIP bundle and manifest in the Versions tab, then publish it to the node store when it is ready for users.\",\n namePlaceholder: \"HTTP Request\",\n cardDescription:\n \"Build custom FluidGrids nodes that users can drag into their visual workflows.\",\n cardBullets: [\n \"ZIP bundle + manifest\",\n \"Custom inputs/outputs\",\n \"Versioned releases\",\n \"Publish to node store\",\n ],\n },\n workflow: {\n appType: \"WORKFLOW\",\n icon: GitFork,\n label: \"Workflow\",\n pageDescription: \"Register a new reusable automation workflow\",\n purpose:\n \"Give your workflow a name and description. Once created you can upload your workflow definition in the Versions tab, then publish it to the store or generate a shareable public link.\",\n namePlaceholder: \"Onboarding Flow\",\n cardDescription:\n \"Build reusable automation workflows that users can import and customize.\",\n cardBullets: [\n \"Manifest + workflow JSON\",\n \"Trigger definitions\",\n \"Versioned releases\",\n \"Publish to workflow store\",\n ],\n },\n parser: {\n appType: \"PARSER\",\n icon: FileCode2,\n label: \"Parser\",\n pageDescription: \"Register a new data parser\",\n purpose:\n \"Give your parser a name and description. Once created you can upload its manifest in the Versions tab, then publish it to the store when it is ready for users.\",\n namePlaceholder: \"CSV Parser\",\n cardDescription:\n \"Build parsers that extract structured data from files or feeds for use across the platform.\",\n cardBullets: [\n \"Manifest JSON only\",\n \"No ZIP bundle needed\",\n \"Versioned releases\",\n \"Publish to store\",\n ],\n },\n widget: {\n appType: \"WIDGET\",\n icon: LayoutGrid,\n label: \"Widget\",\n pageDescription: \"Register a new embeddable widget\",\n purpose:\n \"Give your widget a name and description. Once created you can upload its manifest in the Versions tab, then publish it to the store when it is ready for users.\",\n namePlaceholder: \"Revenue Chart Widget\",\n cardDescription:\n \"Build embeddable UI widgets users can add to dashboards and pages.\",\n cardBullets: [\n \"Manifest JSON only\",\n \"Embeddable UI component\",\n \"Versioned releases\",\n \"Publish to store\",\n ],\n },\n dashboard: {\n appType: \"DASHBOARD\",\n icon: LayoutDashboard,\n label: \"Dashboard Template\",\n pageDescription: \"Register a new dashboard template\",\n purpose:\n \"Give your dashboard template a name and description. Once created you can upload its manifest in the Versions tab, then publish it to the store when it is ready for users.\",\n namePlaceholder: \"Sales Overview Dashboard\",\n cardDescription:\n \"Build prebuilt dashboard layouts users can install and customize.\",\n cardBullets: [\n \"Manifest JSON only\",\n \"Prebuilt layout\",\n \"Versioned releases\",\n \"Publish to store\",\n ],\n },\n};\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 BASE_VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public — listed in the marketplace after review\" },\n { value: \"PRIVATE\", label: \"Private — only you (the creator) can see it\" },\n { value: \"INTERNAL\", label: \"Internal — visible to your entire dev org\" },\n];\n\nconst WORKFLOW_VISIBILITY_OPTIONS = [\n ...BASE_VISIBILITY_OPTIONS,\n {\n value: \"UNLISTED\",\n label: \"Unlisted — shareable via link, not listed in store\",\n },\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// ─── Compatible Products options ──────────────────────────────────────────────\n\nconst COMPATIBLE_PRODUCTS = [\n { value: \"fluidgrids\", label: \"FluidGrids\" },\n { value: \"workspaces\", label: \"Workspaces\" },\n { value: \"bigconsole\", label: \"BigConsole\" },\n { value: \"botlit\", label: \"Botlit\" },\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 category?: string;\n compatibleProducts?: string;\n _submit?: 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 [compatibleProducts, setCompatibleProducts] = useState<string[]>([]);\n const [permissions, setPermissions] =\n useState<PermissionMap>(defaultPermissions());\n // Track whether the user has manually edited the slug so we stop auto-generating\n const slugUserEdited = useRef(false);\n const [formData, setFormData] = useState<FormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n category: \"\",\n visibility: \"PUBLIC\",\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 // Only auto-generate slug while the user hasn't manually touched the field\n slug: slugUserEdited.current ? prev.slug : generateSlug(name),\n }));\n if (errors.name) setErrors((p) => ({ ...p, name: undefined }));\n };\n\n const handleSlugChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n slugUserEdited.current = true;\n const value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, \"\");\n setFormData((prev) => ({ ...prev, slug: value }));\n if (errors.slug) setErrors((p) => ({ ...p, slug: undefined }));\n };\n\n const handleChange = (field: keyof FormData, value: string) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors] || errors._submit)\n setErrors((p) => ({ ...p, [field]: undefined, _submit: 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 if (!formData.category) {\n newErrors.category = \"Category is required\";\n }\n if (builderType !== \"app\" && compatibleProducts.length === 0) {\n newErrors.compatibleProducts =\n \"Select at least one compatible product so users can find this in the right store\";\n }\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const appTypeForBuilder = () =>\n BUILDER_TYPE_META[builderType ?? \"app\"].appType;\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\" | \"PRIVATE\" | \"UNLISTED\" | 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 compatibleProducts:\n compatibleProducts.length > 0 ? compatibleProducts : 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 // Collect all error messages from GraphQL errors and the top-level message\n const messages: string[] = [];\n if (\n error &&\n typeof error === \"object\" &&\n \"graphQLErrors\" in error &&\n Array.isArray((error as { graphQLErrors: unknown[] }).graphQLErrors)\n ) {\n (\n error as { graphQLErrors: Array<{ message: string }> }\n ).graphQLErrors.forEach((e) => messages.push(e.message));\n }\n if (error instanceof Error) messages.push(error.message);\n\n const combined = messages.join(\" \").toLowerCase();\n const isSlugConflict =\n combined.includes(\"slug\") ||\n combined.includes(\"already exist\") ||\n combined.includes(\"unique\") ||\n combined.includes(\"conflict\") ||\n combined.includes(\"duplicate\");\n\n if (isSlugConflict) {\n setErrors((p) => ({\n ...p,\n slug: \"This slug is already taken — choose a different one\",\n }));\n } else {\n setErrors((p) => ({\n ...p,\n _submit: messages[0] ?? \"Something went wrong. Please try again.\",\n }));\n }\n }\n };\n\n // ── Step 1: type picker ─────────────────────────────────────────────────────\n if (!builderType) {\n return (\n <PermissionGuard authOnly>\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 {(\n [\n \"app\",\n \"node\",\n \"workflow\",\n \"parser\",\n \"widget\",\n \"dashboard\",\n ] as BuilderType[]\n ).map((type) => {\n const meta = BUILDER_TYPE_META[type];\n return (\n <TypeCard\n key={type}\n icon={meta.icon}\n title={meta.label}\n description={meta.cardDescription}\n bullets={meta.cardBullets}\n selected={builderType === type}\n onClick={() => setBuilderType(type)}\n />\n );\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 activeMeta = BUILDER_TYPE_META[builderType ?? \"app\"];\n const typeLabel = activeMeta.label;\n const TypeIconComponent = activeMeta.icon;\n const typeIcon = <TypeIconComponent size={16} />;\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={`Create ${typeLabel}`}\n description={activeMeta.pageDescription}\n purpose={activeMeta.purpose}\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={activeMeta.namePlaceholder}\n required\n error={errors.name}\n />\n <InputField\n label=\"Slug\"\n value={formData.slug}\n onChange={handleSlugChange}\n placeholder=\"my-integration\"\n required\n error={errors.slug}\n hint={\n slugUserEdited.current\n ? \"Only lowercase letters, numbers, and hyphens\"\n : \"Auto-generated from name — edit to customise\"\n }\n />\n <MarkdownEditor\n label=\"Short Description\"\n value={formData.shortDescription}\n onChange={(value) => handleChange(\"shortDescription\", value)}\n placeholder=\"Briefly describe what this does\"\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 characters`}\n rows={2}\n maxLength={200}\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 placeholder=\"Select a category…\"\n required\n error={errors.category}\n hint=\"Helps users discover your item in the store\"\n />\n <SelectField\n label=\"Visibility\"\n value={formData.visibility}\n onChange={(e) => handleChange(\"visibility\", e.target.value)}\n options={\n builderType === \"workflow\"\n ? WORKFLOW_VISIBILITY_OPTIONS\n : BASE_VISIBILITY_OPTIONS\n }\n required\n hint={\n formData.visibility === \"PUBLIC\"\n ? \"Listed in the marketplace after review approval — anyone can discover and install it\"\n : formData.visibility === \"UNLISTED\"\n ? \"Not in the store, but share with anyone via a direct link. You can still submit for marketplace review later\"\n : formData.visibility === \"INTERNAL\"\n ? \"Visible to all members of your developer organization — no one outside can access it\"\n : \"Only you (the creator) can see this — other org members cannot access it\"\n }\n />\n </div>\n\n {/* Compatible Products — NODE and WORKFLOW only */}\n {builderType !== \"app\" && (\n <div className=\"space-y-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Compatible Products\n <span className=\"text-status-error-text ml-1\">*</span>\n </label>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Select which products this {builderType} works with. Users\n will see it in those products&apos; node stores.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-3\">\n {COMPATIBLE_PRODUCTS.map((product) => {\n const checked = compatibleProducts.includes(\n product.value,\n );\n return (\n <label\n key={product.value}\n className={cn(\n \"flex items-center gap-2 cursor-pointer rounded-md border px-3 py-2 text-sm transition-colors\",\n checked\n ? \"border-primary bg-primary/10 text-text-primary\"\n : \"border-border-subtle hover:border-border text-text-secondary hover:text-text-primary\",\n errors.compatibleProducts &&\n !checked &&\n \"border-status-error-bg/50\",\n )}\n >\n <input\n type=\"checkbox\"\n className=\"sr-only\"\n checked={checked}\n onChange={() => {\n setCompatibleProducts((prev) => {\n const next = checked\n ? prev.filter((v) => v !== product.value)\n : [...prev, product.value];\n if (next.length > 0)\n setErrors((p) => ({\n ...p,\n compatibleProducts: undefined,\n }));\n return next;\n });\n }}\n />\n {checked && (\n <Check\n size={14}\n className=\"text-primary flex-shrink-0\"\n />\n )}\n {product.label}\n </label>\n );\n })}\n </div>\n {errors.compatibleProducts && (\n <p className=\"text-xs text-status-error-text\">\n {errors.compatibleProducts}\n </p>\n )}\n </div>\n )}\n\n <MarkdownEditor\n label=\"Full Description\"\n value={formData.description}\n onChange={(value) => handleChange(\"description\", value)}\n placeholder=\"Detailed description, features, and use cases...\"\n rows={5}\n hint=\"Markdown supported\"\n />\n </div>\n </PageSection>\n\n {/* Developer Organization — only needed if user has multiple orgs */}\n {orgOptions.length > 2 && (\n <PageSection\n title=\"Organization\"\n description=\"Which developer organization owns this?\"\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 </PageSection>\n )}\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 {/* Integration Setup — App type only */}\n {builderType === \"app\" && (\n <PageSection\n title=\"Integration Setup\"\n description=\"Configure the endpoints for your integration. After a user installs, we redirect them to your callback URL so your server can complete the connection.\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Post-install Callback URL\"\n type=\"url\"\n value={formData.oauthAuthUrl}\n onChange={(e) => handleChange(\"oauthAuthUrl\", e.target.value)}\n placeholder=\"https://yourapp.com/callback\"\n hint=\"Users land here after install with ?installation_id=xxx appended\"\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=\"We POST to this URL when a user uninstalls your integration\"\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 ${typeLabel}, go to the Versions tab to upload its manifest JSON.`}\n </p>\n </div>\n </div>\n )}\n\n {/* General submit error */}\n {errors._submit && (\n <div className=\"flex items-start gap-3 rounded-lg border border-status-error-bg bg-status-error-bg/10 px-4 py-3 text-sm text-status-error-text\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <span>{errors._submit}</span>\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":";;;;;;;;;;;;;;AA+CA,IAAM,IAA0D;CAC9D,KAAK;EACH,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,MAAM;EACJ,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,UAAU;EACR,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,QAAQ;EACN,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,QAAQ;EACN,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,WAAW;EACT,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACF,EAIK,IAAiB;CACrB;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP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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, useRef } from \"react\";\nimport {\n ArrowLeft,\n Box,\n Check,\n ChevronRight,\n FileCode2,\n GitFork,\n Info,\n LayoutDashboard,\n LayoutGrid,\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 { InputField, SelectField } from \"../components/FormField\";\nimport { MarkdownEditor } from \"../components/MarkdownEditor\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\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 =\n | \"app\"\n | \"node\"\n | \"workflow\"\n | \"parser\"\n | \"widget\"\n | \"dashboard\";\n\ninterface BuilderTypeMeta {\n appType:\n | \"API_INTEGRATION\"\n | \"NODE\"\n | \"WORKFLOW\"\n | \"PARSER\"\n | \"WIDGET\"\n | \"DASHBOARD\";\n icon: FC<{ size?: number; className?: string }>;\n label: string;\n pageDescription: string;\n purpose: string;\n namePlaceholder: string;\n cardDescription: string;\n cardBullets: string[];\n}\n\nconst BUILDER_TYPE_META: Record<BuilderType, BuilderTypeMeta> = {\n app: {\n appType: \"API_INTEGRATION\",\n icon: Box,\n label: \"App\",\n pageDescription: \"Register a new app integration with OAuth permissions\",\n purpose:\n \"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 namePlaceholder: \"My Integration\",\n cardDescription:\n \"Build integrations that connect your service to the platform using OAuth tokens.\",\n cardBullets: [\n \"GitHub-style permissions\",\n \"OAuth token flow\",\n \"Webhook events\",\n \"Publish to app store\",\n ],\n },\n node: {\n appType: \"NODE\",\n icon: Puzzle,\n label: \"Node\",\n pageDescription: \"Register a new FluidGrids node package\",\n purpose:\n \"Give your node a name and description. Once created you can upload your ZIP bundle and manifest in the Versions tab, then publish it to the node store when it is ready for users.\",\n namePlaceholder: \"HTTP Request\",\n cardDescription:\n \"Build custom FluidGrids nodes that users can drag into their visual workflows.\",\n cardBullets: [\n \"ZIP bundle + manifest\",\n \"Custom inputs/outputs\",\n \"Versioned releases\",\n \"Publish to node store\",\n ],\n },\n workflow: {\n appType: \"WORKFLOW\",\n icon: GitFork,\n label: \"Workflow\",\n pageDescription: \"Register a new reusable automation workflow\",\n purpose:\n \"Give your workflow a name and description. Once created you can upload your workflow definition in the Versions tab, then publish it to the store or generate a shareable public link.\",\n namePlaceholder: \"Onboarding Flow\",\n cardDescription:\n \"Build reusable automation workflows that users can import and customize.\",\n cardBullets: [\n \"Manifest + workflow JSON\",\n \"Trigger definitions\",\n \"Versioned releases\",\n \"Publish to workflow store\",\n ],\n },\n parser: {\n appType: \"PARSER\",\n icon: FileCode2,\n label: \"Parser\",\n pageDescription: \"Register a new data parser\",\n purpose:\n \"Give your parser a name and description. Once created you can upload its manifest in the Versions tab, then publish it to the store when it is ready for users.\",\n namePlaceholder: \"CSV Parser\",\n cardDescription:\n \"Build parsers that extract structured data from files or feeds for use across the platform.\",\n cardBullets: [\n \"Manifest JSON only\",\n \"No ZIP bundle needed\",\n \"Versioned releases\",\n \"Publish to store\",\n ],\n },\n widget: {\n appType: \"WIDGET\",\n icon: LayoutGrid,\n label: \"Widget\",\n pageDescription: \"Register a new embeddable widget\",\n purpose:\n \"Give your widget a name and description. Once created you can upload its manifest in the Versions tab, then publish it to the store when it is ready for users.\",\n namePlaceholder: \"Revenue Chart Widget\",\n cardDescription:\n \"Build embeddable UI widgets users can add to dashboards and pages.\",\n cardBullets: [\n \"Manifest JSON only\",\n \"Embeddable UI component\",\n \"Versioned releases\",\n \"Publish to store\",\n ],\n },\n dashboard: {\n appType: \"DASHBOARD\",\n icon: LayoutDashboard,\n label: \"Dashboard Template\",\n pageDescription: \"Register a new dashboard template\",\n purpose:\n \"Give your dashboard template a name and description. Once created you can upload its manifest in the Versions tab, then publish it to the store when it is ready for users.\",\n namePlaceholder: \"Sales Overview Dashboard\",\n cardDescription:\n \"Build prebuilt dashboard layouts users can install and customize.\",\n cardBullets: [\n \"Manifest JSON only\",\n \"Prebuilt layout\",\n \"Versioned releases\",\n \"Publish to store\",\n ],\n },\n};\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 BASE_VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public — listed in the marketplace after review\" },\n { value: \"PRIVATE\", label: \"Private — only you (the creator) can see it\" },\n { value: \"INTERNAL\", label: \"Internal — visible to your entire dev org\" },\n];\n\nconst WORKFLOW_VISIBILITY_OPTIONS = [\n ...BASE_VISIBILITY_OPTIONS,\n {\n value: \"UNLISTED\",\n label: \"Unlisted — shareable via link, not listed in store\",\n },\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// ─── Compatible Products options ──────────────────────────────────────────────\n\nconst COMPATIBLE_PRODUCTS = [\n { value: \"fluidgrids\", label: \"FluidGrids\" },\n { value: \"workspaces\", label: \"Workspaces\" },\n { value: \"bigconsole\", label: \"BigConsole\" },\n { value: \"botlit\", label: \"Botlit\" },\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 category?: string;\n compatibleProducts?: string;\n _submit?: 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 [compatibleProducts, setCompatibleProducts] = useState<string[]>([]);\n const [permissions, setPermissions] =\n useState<PermissionMap>(defaultPermissions());\n // Track whether the user has manually edited the slug so we stop auto-generating\n const slugUserEdited = useRef(false);\n const [formData, setFormData] = useState<FormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n category: \"\",\n visibility: \"PUBLIC\",\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 // Only auto-generate slug while the user hasn't manually touched the field\n slug: slugUserEdited.current ? prev.slug : generateSlug(name),\n }));\n if (errors.name) setErrors((p) => ({ ...p, name: undefined }));\n };\n\n const handleSlugChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n slugUserEdited.current = true;\n const value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, \"\");\n setFormData((prev) => ({ ...prev, slug: value }));\n if (errors.slug) setErrors((p) => ({ ...p, slug: undefined }));\n };\n\n const handleChange = (field: keyof FormData, value: string) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors] || errors._submit)\n setErrors((p) => ({ ...p, [field]: undefined, _submit: 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 if (!formData.category) {\n newErrors.category = \"Category is required\";\n }\n if (builderType !== \"app\" && compatibleProducts.length === 0) {\n newErrors.compatibleProducts =\n \"Select at least one compatible product so users can find this in the right store\";\n }\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const appTypeForBuilder = () =>\n BUILDER_TYPE_META[builderType ?? \"app\"].appType;\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 compatibleProducts:\n compatibleProducts.length > 0 ? compatibleProducts : 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 // Collect all error messages from GraphQL errors and the top-level message\n const messages: string[] = [];\n if (\n error &&\n typeof error === \"object\" &&\n \"graphQLErrors\" in error &&\n Array.isArray((error as { graphQLErrors: unknown[] }).graphQLErrors)\n ) {\n (\n error as { graphQLErrors: Array<{ message: string }> }\n ).graphQLErrors.forEach((e) => messages.push(e.message));\n }\n if (error instanceof Error) messages.push(error.message);\n\n const combined = messages.join(\" \").toLowerCase();\n const isSlugConflict =\n combined.includes(\"slug\") ||\n combined.includes(\"already exist\") ||\n combined.includes(\"unique\") ||\n combined.includes(\"conflict\") ||\n combined.includes(\"duplicate\");\n\n if (isSlugConflict) {\n setErrors((p) => ({\n ...p,\n slug: \"This slug is already taken — choose a different one\",\n }));\n } else {\n setErrors((p) => ({\n ...p,\n _submit: messages[0] ?? \"Something went wrong. Please try again.\",\n }));\n }\n }\n };\n\n // ── Step 1: type picker ─────────────────────────────────────────────────────\n if (!builderType) {\n return (\n <PermissionGuard authOnly>\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 {(\n [\n \"app\",\n \"node\",\n \"workflow\",\n \"parser\",\n \"widget\",\n \"dashboard\",\n ] as BuilderType[]\n ).map((type) => {\n const meta = BUILDER_TYPE_META[type];\n return (\n <TypeCard\n key={type}\n icon={meta.icon}\n title={meta.label}\n description={meta.cardDescription}\n bullets={meta.cardBullets}\n selected={builderType === type}\n onClick={() => setBuilderType(type)}\n />\n );\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 activeMeta = BUILDER_TYPE_META[builderType ?? \"app\"];\n const typeLabel = activeMeta.label;\n const TypeIconComponent = activeMeta.icon;\n const typeIcon = <TypeIconComponent size={16} />;\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={`Create ${typeLabel}`}\n description={activeMeta.pageDescription}\n purpose={activeMeta.purpose}\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={activeMeta.namePlaceholder}\n required\n error={errors.name}\n />\n <InputField\n label=\"Slug\"\n value={formData.slug}\n onChange={handleSlugChange}\n placeholder=\"my-integration\"\n required\n error={errors.slug}\n hint={\n slugUserEdited.current\n ? \"Only lowercase letters, numbers, and hyphens\"\n : \"Auto-generated from name — edit to customise\"\n }\n />\n <MarkdownEditor\n label=\"Short Description\"\n value={formData.shortDescription}\n onChange={(value) => handleChange(\"shortDescription\", value)}\n placeholder=\"Briefly describe what this does\"\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 characters`}\n rows={2}\n maxLength={200}\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 placeholder=\"Select a category…\"\n required\n error={errors.category}\n hint=\"Helps users discover your item in the store\"\n />\n <SelectField\n label=\"Visibility\"\n value={formData.visibility}\n onChange={(e) => handleChange(\"visibility\", e.target.value)}\n options={\n builderType === \"workflow\"\n ? WORKFLOW_VISIBILITY_OPTIONS\n : BASE_VISIBILITY_OPTIONS\n }\n required\n hint={\n formData.visibility === \"PUBLIC\"\n ? \"Listed in the marketplace after review approval — anyone can discover and install it\"\n : formData.visibility === \"UNLISTED\"\n ? \"Not in the store, but share with anyone via a direct link. You can still submit for marketplace review later\"\n : formData.visibility === \"INTERNAL\"\n ? \"Visible to all members of your developer organization — no one outside can access it\"\n : \"Only you (the creator) can see this — other org members cannot access it\"\n }\n />\n </div>\n\n {/* Compatible Products — NODE and WORKFLOW only */}\n {builderType !== \"app\" && (\n <div className=\"space-y-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Compatible Products\n <span className=\"text-status-error-text ml-1\">*</span>\n </label>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Select which products this {builderType} works with. Users\n will see it in those products&apos; node stores.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-3\">\n {COMPATIBLE_PRODUCTS.map((product) => {\n const checked = compatibleProducts.includes(\n product.value,\n );\n return (\n <label\n key={product.value}\n className={cn(\n \"flex items-center gap-2 cursor-pointer rounded-md border px-3 py-2 text-sm transition-colors\",\n checked\n ? \"border-primary bg-primary/10 text-text-primary\"\n : \"border-border-subtle hover:border-border text-text-secondary hover:text-text-primary\",\n errors.compatibleProducts &&\n !checked &&\n \"border-status-error-bg/50\",\n )}\n >\n <input\n type=\"checkbox\"\n className=\"sr-only\"\n checked={checked}\n onChange={() => {\n setCompatibleProducts((prev) => {\n const next = checked\n ? prev.filter((v) => v !== product.value)\n : [...prev, product.value];\n if (next.length > 0)\n setErrors((p) => ({\n ...p,\n compatibleProducts: undefined,\n }));\n return next;\n });\n }}\n />\n {checked && (\n <Check\n size={14}\n className=\"text-primary flex-shrink-0\"\n />\n )}\n {product.label}\n </label>\n );\n })}\n </div>\n {errors.compatibleProducts && (\n <p className=\"text-xs text-status-error-text\">\n {errors.compatibleProducts}\n </p>\n )}\n </div>\n )}\n\n <MarkdownEditor\n label=\"Full Description\"\n value={formData.description}\n onChange={(value) => handleChange(\"description\", value)}\n placeholder=\"Detailed description, features, and use cases...\"\n rows={5}\n hint=\"Markdown supported\"\n />\n </div>\n </PageSection>\n\n {/* Developer Organization — only needed if user has multiple orgs */}\n {orgOptions.length > 2 && (\n <PageSection\n title=\"Organization\"\n description=\"Which developer organization owns this?\"\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 </PageSection>\n )}\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 {/* Integration Setup — App type only */}\n {builderType === \"app\" && (\n <PageSection\n title=\"Integration Setup\"\n description=\"Configure the endpoints for your integration. After a user installs, we redirect them to your callback URL so your server can complete the connection.\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Post-install Callback URL\"\n type=\"url\"\n value={formData.oauthAuthUrl}\n onChange={(e) => handleChange(\"oauthAuthUrl\", e.target.value)}\n placeholder=\"https://yourapp.com/callback\"\n hint=\"Users land here after install with ?installation_id=xxx appended\"\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=\"We POST to this URL when a user uninstalls your integration\"\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 ${typeLabel}, go to the Versions tab to upload its manifest JSON.`}\n </p>\n </div>\n </div>\n )}\n\n {/* General submit error */}\n {errors._submit && (\n <div className=\"flex items-start gap-3 rounded-lg border border-status-error-bg bg-status-error-bg/10 px-4 py-3 text-sm text-status-error-text\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <span>{errors._submit}</span>\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":";;;;;;;;;;;;;;AAyDA,IAAM,IAA0D;CAC9D,KAAK;EACH,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,MAAM;EACJ,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,UAAU;EACR,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,QAAQ;EACN,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,QAAQ;EACN,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACD,WAAW;EACT,SAAS;EACT,MAAM;EACN,OAAO;EACP,iBAAiB;EACjB,SACE;EACF,iBAAiB;EACjB,iBACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CACF,EAIK,IAAiB;CACrB;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EditApplicationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/EditApplicationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAuKhC;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,EAwiCjC,CAAC"}
1
+ {"version":3,"file":"EditApplicationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/EditApplicationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAuKhC;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,EA2iCjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"EditApplicationPage.js","names":[],"sources":["../../../src/devportal/pages/EditApplicationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useParams } from \"react-router\";\nimport {\n Save,\n Trash2,\n AlertTriangle,\n Archive,\n Send,\n AlertCircle,\n AppWindow,\n Power,\n Check,\n Copy,\n Eye,\n EyeOff,\n RefreshCw,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n TagInput,\n CheckboxField,\n} from \"../components/FormField\";\nimport { MarkdownEditor } from \"../components/MarkdownEditor\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { LoadingState } from \"../components/LoadingState\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { Application, AppType, AppVisibility, AppStatus } from \"../types\";\nimport { APP_TYPE_LABELS } from \"../utils/constants\";\nimport {\n useGetApplicationQuery,\n useUpdateApplicationMutation,\n useDeleteApplicationMutation,\n useDeactivateAppMutation,\n useRotateApplicationSigningSecretMutation,\n} from \"../../generated/global-operations\";\n\ninterface EditApplicationFormData {\n name: string;\n slug: string;\n shortDescription: string;\n description: string;\n appType: AppType;\n visibility: AppVisibility;\n category: string;\n tags: string[];\n keywords: string[];\n websiteUrl: string;\n documentationUrl: string;\n supportUrl: string;\n privacyPolicyUrl: string;\n termsOfServiceUrl: string;\n githubUrl: string;\n oauthAuthUrl: string;\n oauthRevokeUrl: string;\n requiresApproval: boolean;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n shortDescription?: string;\n appType?: string;\n category?: string;\n}\n\nconst COMPATIBLE_PRODUCTS = [\n { value: \"fluidgrids\", label: \"FluidGrids\" },\n { value: \"workspaces\", label: \"Workspaces\" },\n { value: \"bigconsole\", label: \"BigConsole\" },\n { value: \"botlit\", label: \"Botlit\" },\n];\n\nconst APP_TYPE_OPTIONS = Object.entries(APP_TYPE_LABELS).map(\n ([value, label]) => ({\n value,\n label,\n }),\n);\n\nconst BASE_VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public — listed in the marketplace after review\" },\n { value: \"PRIVATE\", label: \"Private — only you (the creator) can see it\" },\n { value: \"INTERNAL\", label: \"Internal — visible to your entire dev org\" },\n];\n\nconst WORKFLOW_VISIBILITY_OPTIONS = [\n ...BASE_VISIBILITY_OPTIONS,\n {\n value: \"UNLISTED\",\n label: \"Unlisted — shareable via link, not listed in store\",\n },\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/**\n * Helper to safely map GraphQL response to Application type\n */\nfunction mapToApplication(\n data: NonNullable<\n ReturnType<typeof useGetApplicationQuery>[\"data\"]\n >[\"application\"],\n): Application | null {\n if (!data) return null;\n return {\n id: data.id,\n name: data.name,\n slug: data.slug,\n shortDescription: data.shortDescription || undefined,\n description: data.description || undefined,\n appType: data.appType,\n status: data.status,\n visibility: data.visibility,\n tenantId: data.tenantId,\n developerId: data.developerId,\n developerOrganizationId: data.developerOrganizationId || undefined,\n category: data.category || undefined,\n tags: data.tags || [],\n keywords: data.keywords || [],\n iconUrl: data.iconUrl || undefined,\n websiteUrl: data.websiteUrl || undefined,\n documentationUrl: data.documentationUrl || undefined,\n supportUrl: data.supportUrl || undefined,\n privacyPolicyUrl: data.privacyPolicyUrl || undefined,\n termsOfServiceUrl: data.termsOfServiceUrl || undefined,\n githubUrl: data.githubUrl || undefined,\n pricingType: data.pricingType || undefined,\n installCount: data.installCount,\n activeInstallCount: data.activeInstallCount,\n averageRating: data.averageRating ?? undefined,\n reviewCount: data.reviewCount,\n submittedAt: data.submittedAt || undefined,\n publishedAt: data.publishedAt || undefined,\n lastReviewedAt: data.lastReviewedAt || undefined,\n reviewStatus: data.reviewStatus,\n reviewNotes: data.reviewNotes || undefined,\n reviewedBy: data.reviewedBy || undefined,\n isFeatured: data.isFeatured,\n isVerified: data.isVerified,\n requiresApproval: data.requiresApproval,\n oauthAuthUrl: data.oauthAuthUrl || undefined,\n oauthRevokeUrl: data.oauthRevokeUrl || undefined,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n };\n}\n\n/**\n * Edit application page — wired to real GraphQL mutations.\n */\nexport const EditApplicationPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { appId } = useParams<{ appId: string }>();\n const { t } = useI18n();\n const { allowed: canUpdate } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_UPDATE,\n );\n const { allowed: canDelete } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_DELETE,\n );\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<FormErrors>({});\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showDeprecateDialog, setShowDeprecateDialog] = useState(false);\n const [showDeactivateDialog, setShowDeactivateDialog] = useState(false);\n const [hasChanges, setHasChanges] = useState(false);\n const [showSecret, setShowSecret] = useState(false);\n const [isRotating, setIsRotating] = useState(false);\n const [rotatedSecret, setRotatedSecret] = useState<string | null>(null);\n const [copiedSecret, setCopiedSecret] = useState(false);\n\n // ---------------------------------------------------------------------------\n // GraphQL queries & mutations\n // ---------------------------------------------------------------------------\n const {\n data,\n loading,\n error: queryError,\n refetch,\n } = useGetApplicationQuery({\n variables: { id: appId! },\n skip: !appId,\n });\n\n const [updateApplication] = useUpdateApplicationMutation();\n const [deleteApplication] = useDeleteApplicationMutation();\n const [deactivateApp] = useDeactivateAppMutation();\n const [rotateSigningSecret] = useRotateApplicationSigningSecretMutation();\n\n // ---------------------------------------------------------------------------\n // Derive application from query data\n // ---------------------------------------------------------------------------\n const app = mapToApplication(data?.application);\n\n useEffect(() => {\n const name = app?.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, app?.name]);\n\n // ---------------------------------------------------------------------------\n // Form state — initialised from fetched app once available\n // ---------------------------------------------------------------------------\n const [formData, setFormData] = useState<EditApplicationFormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n appType: \"WEB_APP\",\n visibility: \"PUBLIC\",\n category: \"\",\n tags: [],\n keywords: [],\n websiteUrl: \"\",\n documentationUrl: \"\",\n supportUrl: \"\",\n privacyPolicyUrl: \"\",\n termsOfServiceUrl: \"\",\n githubUrl: \"\",\n oauthAuthUrl: \"\",\n oauthRevokeUrl: \"\",\n requiresApproval: false,\n });\n const [compatibleProducts, setCompatibleProducts] = useState<string[]>([]);\n\n useEffect(() => {\n if (app) {\n setFormData({\n name: app.name,\n slug: app.slug,\n shortDescription: app.shortDescription || \"\",\n description: app.description || \"\",\n appType: app.appType,\n visibility: app.visibility,\n category: app.category || \"\",\n tags: app.tags,\n keywords: app.keywords,\n websiteUrl: app.websiteUrl || \"\",\n documentationUrl: app.documentationUrl || \"\",\n supportUrl: app.supportUrl || \"\",\n privacyPolicyUrl: app.privacyPolicyUrl || \"\",\n termsOfServiceUrl: app.termsOfServiceUrl || \"\",\n githubUrl: app.githubUrl || \"\",\n oauthAuthUrl: app.oauthAuthUrl || \"\",\n oauthRevokeUrl: app.oauthRevokeUrl || \"\",\n requiresApproval: app.requiresApproval,\n });\n // Load compatibleProducts from manifest.compatibility.products\n // Note: mapToApplication doesn't copy manifest, so read from raw query data directly.\n const manifest = data?.application?.manifest as\n Record<string, unknown> | null | undefined;\n const compatibility = manifest?.compatibility as\n Record<string, unknown> | undefined;\n const products = Array.isArray(compatibility?.products)\n ? (compatibility.products as string[])\n : [];\n setCompatibleProducts(products);\n setHasChanges(false);\n }\n // Re-sync form when the underlying application data changes (e.g. after\n // a refetch triggered by a mutation). We intentionally depend on\n // `data?.application` rather than the derived `app` object so that the\n // effect fires when the query cache updates.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [data?.application]);\n\n // ---------------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------------\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleChange = (\n field: keyof EditApplicationFormData,\n value: string | string[] | boolean,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n setHasChanges(true);\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const handleRotateSecret = async () => {\n if (!appId) return;\n setIsRotating(true);\n try {\n const result = await rotateSigningSecret({ variables: { id: appId } });\n const newSecret =\n result.data?.rotateApplicationSigningSecret?.signingSecret;\n if (newSecret) {\n setRotatedSecret(newSecret);\n setShowSecret(true);\n }\n } catch {\n nativeNotify(\"error\");\n } finally {\n setIsRotating(false);\n }\n };\n\n const handleCopySecret = async (secret: string) => {\n await navigator.clipboard.writeText(secret);\n setCopiedSecret(true);\n setTimeout(() => setCopiedSecret(false), 2000);\n };\n\n const validateForm = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.appNameRequired\",\n \"Application name is required\",\n );\n } else if (formData.name.length < 3) {\n newErrors.name = tr(\n \"devportal.appNameMinLength\",\n \"Name must be at least 3 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.appNameMaxLength\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalidChars\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (!formData.shortDescription.trim()) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionRequired\",\n \"Short description is required\",\n );\n } else if (formData.shortDescription.length > 200) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionMaxLength\",\n \"Short description must be less than 200 characters\",\n );\n }\n\n if (!formData.appType) {\n newErrors.appType = tr(\n \"devportal.appTypeRequired\",\n \"Application type is required\",\n );\n }\n\n if (!formData.category) {\n newErrors.category = tr(\n \"devportal.categoryRequired\",\n \"Category is required\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm() || !app) {\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n name: formData.name,\n shortDescription: formData.shortDescription,\n description: formData.description,\n appType: formData.appType,\n visibility: formData.visibility,\n category: formData.category,\n tags: formData.tags,\n keywords: formData.keywords,\n websiteUrl: formData.websiteUrl || null,\n documentationUrl: formData.documentationUrl || null,\n supportUrl: formData.supportUrl || null,\n privacyPolicyUrl: formData.privacyPolicyUrl || null,\n termsOfServiceUrl: formData.termsOfServiceUrl || null,\n githubUrl: formData.githubUrl || null,\n oauthAuthUrl: formData.oauthAuthUrl || null,\n oauthRevokeUrl: formData.oauthRevokeUrl || null,\n compatibleProducts,\n },\n },\n });\n setHasChanges(false);\n } catch (err) {\n console.error(\"Failed to update application:\", err);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleDelete = async () => {\n if (!app) return;\n void nativeImpact(\"medium\");\n try {\n await deleteApplication({\n variables: { id: app.id },\n });\n void nativeNotify(\"success\");\n handleNavigate(\"/apps\");\n } catch (err) {\n void nativeNotify(\"error\");\n console.error(\"Failed to delete application:\", err);\n }\n };\n\n const handleDeprecate = async () => {\n if (!app) return;\n try {\n // Deprecate is modelled as a status transition via update. The backend\n // treats a visibility change to PRIVATE + status mutation or a dedicated\n // deprecation endpoint. Here we use submitApplication with a status note\n // as the deprecate action since there is no separate `deprecateApplication`\n // mutation. If a dedicated mutation is added later, swap it in here.\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n visibility: \"PRIVATE\",\n },\n },\n });\n setShowDeprecateDialog(false);\n } catch (err) {\n console.error(\"Failed to deprecate application:\", err);\n }\n };\n\n const handleDeactivate = async () => {\n if (!app) return;\n try {\n await deactivateApp({\n variables: { id: app.id },\n });\n setShowDeactivateDialog(false);\n handleNavigate(\"/apps\");\n } catch (err) {\n console.error(\"Failed to deactivate application:\", err);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Loading / Error / Not-found states\n // ---------------------------------------------------------------------------\n if (loading) {\n return (\n <LoadingState\n message={tr(\"devportal.loadingApplication\", \"Loading application...\")}\n />\n );\n }\n\n if (queryError) {\n return (\n <PageLayout\n title={tr(\"devportal.error\", \"Error\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle size={48} className=\"text-status-error-text mb-4\" />\n <h3 className=\"text-lg font-semibold mb-2\">\n {tr(\n \"devportal.failedToLoadApplication\",\n \"Failed to load application\",\n )}\n </h3>\n <p className=\"text-sm text-text-secondary\">{queryError.message}</p>\n </div>\n </PageLayout>\n );\n }\n\n if (!app) {\n return (\n <PageLayout\n title={tr(\"devportal.notFound\", \"Not Found\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <EmptyState\n illustration=\"empty-generic\"\n icon={<AppWindow size={48} />}\n title={tr(\"devportal.applicationNotFound\", \"Application not found\")}\n description={tr(\n \"devportal.applicationNotFoundDescription\",\n \"The application you're looking for doesn't exist or you don't have access to it.\",\n )}\n />\n </PageLayout>\n );\n }\n\n // ---------------------------------------------------------------------------\n // Derived flags\n // ---------------------------------------------------------------------------\n const canSubmitForReview =\n app.status === \"DRAFT\" || app.status === \"REJECTED\";\n const canDeprecate = app.status === \"PUBLISHED\";\n const canDeactivate = app.status === \"ACTIVE\" || app.status === \"PUBLISHED\";\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={`${tr(\"devportal.editAppTitlePrefix\", \"Edit:\")} ${app.name}`}\n description={tr(\n \"devportal.editAppDescription\",\n \"Update your application settings\",\n )}\n purpose={tr(\n \"devportal.editAppPurpose\",\n \"Change how this app appears and behaves — its name, description, classification, links, and lifecycle settings. Use the danger zone at the bottom to deprecate or delete it when it is retired.\",\n )}\n backLink={`${basePath}/apps/${app.id}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={app.status} type=\"app\" />\n </div>\n }\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Status banner for published apps */}\n {app.status === \"PUBLISHED\" && (\n <div className=\"rounded-lg border border-border-subtle bg-status-warning-bg-subtle p-4 flex gap-3\">\n <AlertTriangle\n size={20}\n className=\"text-status-warning-text flex-shrink-0 mt-0.5\"\n />\n <div className=\"text-sm text-status-warning-text\">\n <p className=\"font-medium\">\n {tr(\n \"devportal.appIsPublished\",\n \"This application is published\",\n )}\n </p>\n <p className=\"mt-1 text-status-warning-text\">\n {tr(\n \"devportal.publishedAppChangesWarning\",\n \"Changes to published apps may require re-review. Major changes will put the app back into review status.\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {/* Basic Information */}\n <PageSection\n title={tr(\"devportal.basicInformation\", \"Basic Information\")}\n description={tr(\n \"devportal.basicInformationDescription\",\n \"Core details about your application\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.applicationName\", \"Application Name\")}\n value={formData.name}\n onChange={(e) => handleChange(\"name\", e.target.value)}\n placeholder={tr(\n \"devportal.applicationNamePlaceholder\",\n \"My Awesome App\",\n )}\n required\n error={errors.name}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.slugPlaceholder\", \"my-awesome-app\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugChangeWarning\",\n \"Changing the slug may break existing integrations\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.shortDescription\", \"Short Description\")}\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder={tr(\n \"devportal.shortDescriptionPlaceholder\",\n \"A brief description of what your app does\",\n )}\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 ${tr(\"devportal.characters\", \"characters\")}`}\n rows={2}\n />\n\n <MarkdownEditor\n label={tr(\"devportal.fullDescription\", \"Full Description\")}\n value={formData.description}\n onChange={(value) => handleChange(\"description\", value)}\n placeholder={tr(\n \"devportal.fullDescriptionPlaceholder\",\n \"Detailed description of your application, features, and use cases...\",\n )}\n rows={6}\n hint={tr(\n \"devportal.markdownSupported\",\n \"Markdown formatting is supported\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Classification */}\n <PageSection\n title={tr(\"devportal.classification\", \"Classification\")}\n description={tr(\n \"devportal.classificationDescription\",\n \"Categorize your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label={tr(\"devportal.applicationType\", \"Application Type\")}\n value={formData.appType}\n onChange={(e) => handleChange(\"appType\", e.target.value)}\n options={APP_TYPE_OPTIONS}\n required\n error={errors.appType}\n />\n\n <SelectField\n label={tr(\"devportal.category\", \"Category\")}\n value={formData.category}\n onChange={(e) => handleChange(\"category\", e.target.value)}\n options={CATEGORY_OPTIONS}\n required\n error={errors.category}\n />\n\n <SelectField\n label={tr(\"devportal.visibility\", \"Visibility\")}\n value={formData.visibility}\n onChange={(e) =>\n handleChange(\"visibility\", e.target.value as AppVisibility)\n }\n options={\n app?.appType === \"WORKFLOW\"\n ? WORKFLOW_VISIBILITY_OPTIONS\n : BASE_VISIBILITY_OPTIONS\n }\n hint={\n formData.visibility === \"PUBLIC\"\n ? tr(\n \"devportal.visibilityHintPublic\",\n \"Listed in the marketplace after review approval — anyone can discover and install it\",\n )\n : formData.visibility === \"UNLISTED\"\n ? tr(\n \"devportal.visibilityHintUnlisted\",\n \"Not in the store, but accessible via a direct link. You can still submit for marketplace review\",\n )\n : formData.visibility === \"INTERNAL\"\n ? tr(\n \"devportal.visibilityHintInternal\",\n \"Visible to all members of your developer organization — no one outside can access it\",\n )\n : tr(\n \"devportal.visibilityHintPrivate\",\n \"Only you (the creator) can see this — other org members cannot access it\",\n )\n }\n />\n </div>\n\n <TagInput\n label={tr(\"devportal.tags\", \"Tags\")}\n value={formData.tags}\n onChange={(tags) => handleChange(\"tags\", tags)}\n placeholder={tr(\n \"devportal.addTagsPlaceholder\",\n \"Add tags (press Enter)\",\n )}\n />\n\n <TagInput\n label={tr(\"devportal.keywords\", \"Keywords\")}\n value={formData.keywords}\n onChange={(keywords) => handleChange(\"keywords\", keywords)}\n placeholder={tr(\n \"devportal.addKeywordsPlaceholder\",\n \"Add keywords (press Enter)\",\n )}\n />\n\n {/* Compatible Products — NODE and WORKFLOW only */}\n {(formData.appType === \"NODE\" ||\n formData.appType === \"WORKFLOW\") && (\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Compatible Products\n </label>\n <p className=\"text-xs text-text-secondary\">\n Select which products this {formData.appType.toLowerCase()}{\" \"}\n works with. Users will see it in those products&apos; node\n stores.\n </p>\n <div className=\"flex flex-wrap gap-3\">\n {COMPATIBLE_PRODUCTS.map((product) => {\n const checked = compatibleProducts.includes(product.value);\n return (\n <label\n key={product.value}\n className={cn(\n \"flex items-center gap-2 cursor-pointer rounded-md border px-3 py-2 text-sm transition-colors\",\n checked\n ? \"border-primary bg-primary/10 text-text-primary\"\n : \"border-border-subtle hover:border-border text-text-secondary hover:text-text-primary\",\n )}\n >\n <input\n type=\"checkbox\"\n className=\"sr-only\"\n checked={checked}\n onChange={() => {\n setCompatibleProducts((prev) =>\n checked\n ? prev.filter((v) => v !== product.value)\n : [...prev, product.value],\n );\n setHasChanges(true);\n }}\n />\n {checked && (\n <Check\n size={14}\n className=\"text-primary flex-shrink-0\"\n />\n )}\n {product.label}\n </label>\n );\n })}\n </div>\n </div>\n )}\n </PageSection>\n\n {/* URLs */}\n <PageSection\n title={tr(\"devportal.links\", \"Links\")}\n description={tr(\n \"devportal.linksDescription\",\n \"External resources for your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.websiteUrlPlaceholder\",\n \"https://example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.documentationUrl\", \"Documentation URL\")}\n type=\"url\"\n value={formData.documentationUrl}\n onChange={(e) =>\n handleChange(\"documentationUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.documentationUrlPlaceholder\",\n \"https://docs.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.supportUrl\", \"Support URL\")}\n type=\"url\"\n value={formData.supportUrl}\n onChange={(e) => handleChange(\"supportUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.supportUrlPlaceholder\",\n \"https://support.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.githubUrl\", \"GitHub URL\")}\n type=\"url\"\n value={formData.githubUrl}\n onChange={(e) => handleChange(\"githubUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.githubUrlPlaceholder\",\n \"https://github.com/org/repo\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.privacyPolicyUrl\", \"Privacy Policy URL\")}\n type=\"url\"\n value={formData.privacyPolicyUrl}\n onChange={(e) =>\n handleChange(\"privacyPolicyUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.privacyPolicyUrlPlaceholder\",\n \"https://example.com/privacy\",\n )}\n />\n\n <InputField\n label={tr(\n \"devportal.termsOfServiceUrl\",\n \"Terms of Service URL\",\n )}\n type=\"url\"\n value={formData.termsOfServiceUrl}\n onChange={(e) =>\n handleChange(\"termsOfServiceUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.termsOfServiceUrlPlaceholder\",\n \"https://example.com/terms\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Integration Setup — INTEGRATION / API_INTEGRATION only */}\n {(formData.appType === \"INTEGRATION\" ||\n formData.appType === \"API_INTEGRATION\") && (\n <PageSection\n title=\"Integration Setup\"\n description=\"Configure the endpoints for your integration. After a user installs, we redirect them to your callback URL so your server can complete the connection.\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Post-install Callback URL\"\n type=\"url\"\n value={formData.oauthAuthUrl}\n onChange={(e) => handleChange(\"oauthAuthUrl\", e.target.value)}\n placeholder=\"https://yourapp.com/callback\"\n hint=\"Users land here after install with ?installation_id=xxx appended\"\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=\"We POST to this URL when a user uninstalls your integration\"\n />\n {/* Signing secret — read-only, rotate-only */}\n {(() => {\n const displaySecret =\n rotatedSecret ?? data?.application?.signingSecret;\n const masked = \"boff_sk_\" + \"•\".repeat(32);\n return (\n <div className=\"space-y-1.5\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Signing Secret\n </label>\n <p className=\"text-xs text-text-secondary\">\n Use this secret to authenticate{\" \"}\n <code className=\"font-mono text-xs bg-bg-sunken px-1 py-0.5 rounded\">\n verifyInstallation\n </code>{\" \"}\n calls from your integration server. Store it securely —\n it is only shown immediately after generation or\n rotation.\n </p>\n {displaySecret ? (\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1 flex items-center gap-0 rounded-md border border-border-default bg-bg-sunken overflow-hidden\">\n <code className=\"flex-1 px-3 py-2 text-xs font-mono text-text-primary truncate\">\n {showSecret ? displaySecret : masked}\n </code>\n <button\n type=\"button\"\n onClick={() => setShowSecret((v) => !v)}\n className=\"px-2 py-2 text-text-secondary hover:text-text-primary border-l border-border-default\"\n title={showSecret ? \"Hide\" : \"Reveal\"}\n >\n {showSecret ? (\n <EyeOff size={14} />\n ) : (\n <Eye size={14} />\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => handleCopySecret(displaySecret)}\n className=\"px-2 py-2 text-text-secondary hover:text-text-primary border-l border-border-default\"\n title=\"Copy to clipboard\"\n >\n {copiedSecret ? (\n <Check size={14} className=\"text-green-500\" />\n ) : (\n <Copy size={14} />\n )}\n </button>\n </div>\n <button\n type=\"button\"\n onClick={handleRotateSecret}\n disabled={isRotating}\n className=\"flex items-center gap-1.5 px-3 py-2 text-xs rounded-md border border-border-default bg-bg-default text-text-primary hover:bg-bg-subtle disabled:opacity-50\"\n title=\"Rotate signing secret\"\n >\n <RefreshCw\n size={12}\n className={isRotating ? \"animate-spin\" : \"\"}\n />\n Rotate\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={handleRotateSecret}\n disabled={isRotating}\n className=\"flex items-center gap-1.5 px-3 py-2 text-xs rounded-md border border-border-default bg-bg-default text-text-primary hover:bg-bg-subtle disabled:opacity-50\"\n >\n <RefreshCw\n size={12}\n className={isRotating ? \"animate-spin\" : \"\"}\n />\n Generate Signing Secret\n </button>\n )}\n {rotatedSecret && (\n <p className=\"text-xs text-amber-600 dark:text-amber-400\">\n New secret generated. Copy it now — the old secret is\n already invalidated.\n </p>\n )}\n </div>\n );\n })()}\n </div>\n </PageSection>\n )}\n\n {/* Settings */}\n <PageSection\n title={tr(\"devportal.settings\", \"Settings\")}\n description={tr(\n \"devportal.settingsDescription\",\n \"Additional configuration options\",\n )}\n >\n <div className=\"space-y-4\">\n <CheckboxField\n label={tr(\n \"devportal.requireWorkspaceAdminApproval\",\n \"Require workspace admin approval\",\n )}\n description={tr(\n \"devportal.requireWorkspaceAdminApprovalDescription\",\n \"When enabled, workspace admins must approve installations before users can access this app\",\n )}\n checked={formData.requiresApproval}\n onChange={(e) =>\n handleChange(\"requiresApproval\", e.target.checked)\n }\n />\n </div>\n </PageSection>\n\n {/* Submission readiness checklist */}\n {canSubmitForReview && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken p-4 space-y-2\">\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <Send size={14} />\n {tr(\"devportal.submissionChecklist\", \"Submission Checklist\")}\n </p>\n {[\n {\n label: tr(\"devportal.checkName\", \"Name set\"),\n ok: !!formData.name.trim(),\n },\n {\n label: tr(\n \"devportal.checkShortDesc\",\n \"Short description set\",\n ),\n ok: !!formData.shortDescription.trim(),\n },\n {\n label: tr(\n \"devportal.checkFullDesc\",\n \"Full description added\",\n ),\n ok: !!formData.description.trim(),\n },\n {\n label: tr(\"devportal.checkCategory\", \"Category selected\"),\n ok: !!formData.category,\n },\n {\n label: tr(\n \"devportal.checkVisibility\",\n \"Visibility set to Public or Unlisted\",\n ),\n ok:\n !!formData.visibility && formData.visibility !== \"PRIVATE\",\n },\n {\n label: tr(\"devportal.checkSaved\", \"All changes saved\"),\n ok: !hasChanges,\n },\n ...(app?.appType === \"NODE\" || app?.appType === \"WORKFLOW\"\n ? [\n {\n label: tr(\n \"devportal.checkVersion\",\n \"At least one version uploaded\",\n ),\n ok: (data?.application?.versions?.totalCount ?? 0) > 0,\n },\n ]\n : []),\n ].map((item) => (\n <div\n key={item.label}\n className=\"flex items-center gap-2 text-sm\"\n >\n {item.ok ? (\n <span className=\"text-status-success-text\">✓</span>\n ) : (\n <span className=\"text-status-error-text\">✗</span>\n )}\n <span\n className={\n item.ok\n ? \"text-text-secondary line-through\"\n : \"text-text-primary\"\n }\n >\n {item.label}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col gap-4 border-t pt-6\">\n {/* Primary actions */}\n <div className=\"flex items-center justify-end\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !hasChanges || !canUpdate}\n aria-disabled={!canUpdate}\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 <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.saving\", \"Saving...\")}\n </>\n ) : (\n <>\n <Save size={16} />\n {tr(\"devportal.saveChanges\", \"Save Changes\")}\n </>\n )}\n </button>\n </div>\n\n {/* Danger zone */}\n <div className=\"rounded-lg border border-border-subtle bg-status-error-bg-subtle p-4\">\n <h3 className=\"text-sm font-medium text-status-error-text\">\n {tr(\"devportal.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-error-text\">\n {tr(\n \"devportal.dangerZoneWarning\",\n \"These actions are irreversible. Please proceed with caution.\",\n )}\n </p>\n <div className=\"mt-4 flex gap-3\">\n {canDeprecate && (\n <button\n type=\"button\"\n onClick={() => setShowDeprecateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Archive size={14} />\n {tr(\"devportal.deprecateApp\", \"Deprecate App\")}\n </button>\n )}\n {canDeactivate && (\n <button\n type=\"button\"\n onClick={() => setShowDeactivateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Power size={14} />\n {tr(\"devportal.deactivateApp\", \"Deactivate App\")}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setShowDeleteDialog(true)}\n disabled={!canDelete}\n aria-disabled={!canDelete}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg bg-bg-surface px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n <Trash2 size={14} />\n {tr(\"devportal.deleteApp\", \"Delete App\")}\n </button>\n </div>\n </div>\n </div>\n </form>\n\n {/* Delete confirmation dialog */}\n <ConfirmDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n title={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n description={tr(\n \"devportal.deleteApplicationConfirm\",\n `Are you sure you want to delete \"${app.name}\"? This action cannot be undone. All OAuth clients, API keys, webhooks, and analytics data will be permanently deleted.`,\n )}\n confirmLabel={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n variant=\"destructive\"\n onConfirm={handleDelete}\n />\n\n {/* Deprecate confirmation dialog */}\n <ConfirmDialog\n open={showDeprecateDialog}\n onOpenChange={setShowDeprecateDialog}\n title={tr(\"devportal.deprecateApplication\", \"Deprecate Application\")}\n description={tr(\n \"devportal.deprecateApplicationConfirm\",\n `Are you sure you want to deprecate \"${app.name}\"? Existing users will still be able to use the app, but it will no longer be visible in the marketplace and new installations will be disabled.`,\n )}\n confirmLabel={tr(\n \"devportal.deprecateApplication\",\n \"Deprecate Application\",\n )}\n variant=\"warning\"\n onConfirm={handleDeprecate}\n />\n\n {/* Deactivate confirmation dialog */}\n <ConfirmDialog\n open={showDeactivateDialog}\n onOpenChange={setShowDeactivateDialog}\n title={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n description={tr(\n \"devportal.deactivateApplicationConfirm\",\n `Are you sure you want to deactivate \"${app.name}\"? This will revoke all active tokens and disable the app from the auth-svc layer. The app will no longer be able to authenticate or act on behalf of users.`,\n )}\n confirmLabel={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n variant=\"destructive\"\n onConfirm={handleDeactivate}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditApplicationPage.js","names":[],"sources":["../../../src/devportal/pages/EditApplicationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useParams } from \"react-router\";\nimport {\n Save,\n Trash2,\n AlertTriangle,\n Archive,\n Send,\n AlertCircle,\n AppWindow,\n Power,\n Check,\n Copy,\n Eye,\n EyeOff,\n RefreshCw,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n TagInput,\n CheckboxField,\n} from \"../components/FormField\";\nimport { MarkdownEditor } from \"../components/MarkdownEditor\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { LoadingState } from \"../components/LoadingState\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { cn } from \"../utils/cn\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { Application, AppType, AppVisibility, AppStatus } from \"../types\";\nimport { APP_TYPE_LABELS } from \"../utils/constants\";\nimport {\n useGetApplicationQuery,\n useUpdateApplicationMutation,\n useDeleteApplicationMutation,\n useDeactivateAppMutation,\n useRotateApplicationSigningSecretMutation,\n} from \"../../generated/global-operations\";\n\ninterface EditApplicationFormData {\n name: string;\n slug: string;\n shortDescription: string;\n description: string;\n appType: AppType;\n visibility: AppVisibility;\n category: string;\n tags: string[];\n keywords: string[];\n websiteUrl: string;\n documentationUrl: string;\n supportUrl: string;\n privacyPolicyUrl: string;\n termsOfServiceUrl: string;\n githubUrl: string;\n oauthAuthUrl: string;\n oauthRevokeUrl: string;\n requiresApproval: boolean;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n shortDescription?: string;\n appType?: string;\n category?: string;\n}\n\nconst COMPATIBLE_PRODUCTS = [\n { value: \"fluidgrids\", label: \"FluidGrids\" },\n { value: \"workspaces\", label: \"Workspaces\" },\n { value: \"bigconsole\", label: \"BigConsole\" },\n { value: \"botlit\", label: \"Botlit\" },\n];\n\nconst APP_TYPE_OPTIONS = Object.entries(APP_TYPE_LABELS).map(\n ([value, label]) => ({\n value,\n label,\n }),\n);\n\nconst BASE_VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public — listed in the marketplace after review\" },\n { value: \"PRIVATE\", label: \"Private — only you (the creator) can see it\" },\n { value: \"INTERNAL\", label: \"Internal — visible to your entire dev org\" },\n];\n\nconst WORKFLOW_VISIBILITY_OPTIONS = [\n ...BASE_VISIBILITY_OPTIONS,\n {\n value: \"UNLISTED\",\n label: \"Unlisted — shareable via link, not listed in store\",\n },\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/**\n * Helper to safely map GraphQL response to Application type\n */\nfunction mapToApplication(\n data: NonNullable<\n ReturnType<typeof useGetApplicationQuery>[\"data\"]\n >[\"application\"],\n): Application | null {\n if (!data) return null;\n return {\n id: data.id,\n name: data.name,\n slug: data.slug,\n shortDescription: data.shortDescription || undefined,\n description: data.description || undefined,\n appType: data.appType,\n status: data.status,\n visibility: data.visibility,\n tenantId: data.tenantId,\n developerId: data.developerId,\n developerOrganizationId: data.developerOrganizationId || undefined,\n category: data.category || undefined,\n tags: data.tags || [],\n keywords: data.keywords || [],\n iconUrl: data.iconUrl || undefined,\n websiteUrl: data.websiteUrl || undefined,\n documentationUrl: data.documentationUrl || undefined,\n supportUrl: data.supportUrl || undefined,\n privacyPolicyUrl: data.privacyPolicyUrl || undefined,\n termsOfServiceUrl: data.termsOfServiceUrl || undefined,\n githubUrl: data.githubUrl || undefined,\n pricingType: data.pricingType || undefined,\n installCount: data.installCount,\n activeInstallCount: data.activeInstallCount,\n averageRating: data.averageRating ?? undefined,\n reviewCount: data.reviewCount,\n submittedAt: data.submittedAt || undefined,\n publishedAt: data.publishedAt || undefined,\n lastReviewedAt: data.lastReviewedAt || undefined,\n reviewStatus: data.reviewStatus,\n reviewNotes: data.reviewNotes || undefined,\n reviewedBy: data.reviewedBy || undefined,\n isFeatured: data.isFeatured,\n isVerified: data.isVerified,\n requiresApproval: data.requiresApproval,\n oauthAuthUrl: data.oauthAuthUrl || undefined,\n oauthRevokeUrl: data.oauthRevokeUrl || undefined,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n };\n}\n\n/**\n * Edit application page — wired to real GraphQL mutations.\n */\nexport const EditApplicationPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { appId } = useParams<{ appId: string }>();\n const { t } = useI18n();\n const { allowed: canUpdate } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_UPDATE,\n );\n const { allowed: canDelete } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_DELETE,\n );\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<FormErrors>({});\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showDeprecateDialog, setShowDeprecateDialog] = useState(false);\n const [showDeactivateDialog, setShowDeactivateDialog] = useState(false);\n const [hasChanges, setHasChanges] = useState(false);\n const [showSecret, setShowSecret] = useState(false);\n const [isRotating, setIsRotating] = useState(false);\n const [rotatedSecret, setRotatedSecret] = useState<string | null>(null);\n const [copiedSecret, setCopiedSecret] = useState(false);\n\n // ---------------------------------------------------------------------------\n // GraphQL queries & mutations\n // ---------------------------------------------------------------------------\n const {\n data,\n loading,\n error: queryError,\n refetch,\n } = useGetApplicationQuery({\n variables: { id: appId! },\n skip: !appId,\n });\n\n const [updateApplication] = useUpdateApplicationMutation();\n const [deleteApplication] = useDeleteApplicationMutation();\n const [deactivateApp] = useDeactivateAppMutation();\n const [rotateSigningSecret] = useRotateApplicationSigningSecretMutation();\n\n // ---------------------------------------------------------------------------\n // Derive application from query data\n // ---------------------------------------------------------------------------\n const app = mapToApplication(data?.application);\n\n useEffect(() => {\n const name = app?.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, app?.name]);\n\n // ---------------------------------------------------------------------------\n // Form state — initialised from fetched app once available\n // ---------------------------------------------------------------------------\n const [formData, setFormData] = useState<EditApplicationFormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n appType: \"WEB_APP\",\n visibility: \"PUBLIC\",\n category: \"\",\n tags: [],\n keywords: [],\n websiteUrl: \"\",\n documentationUrl: \"\",\n supportUrl: \"\",\n privacyPolicyUrl: \"\",\n termsOfServiceUrl: \"\",\n githubUrl: \"\",\n oauthAuthUrl: \"\",\n oauthRevokeUrl: \"\",\n requiresApproval: false,\n });\n const [compatibleProducts, setCompatibleProducts] = useState<string[]>([]);\n\n useEffect(() => {\n if (app) {\n setFormData({\n name: app.name,\n slug: app.slug,\n shortDescription: app.shortDescription || \"\",\n description: app.description || \"\",\n appType: app.appType,\n visibility: app.visibility,\n category: app.category || \"\",\n tags: app.tags,\n keywords: app.keywords,\n websiteUrl: app.websiteUrl || \"\",\n documentationUrl: app.documentationUrl || \"\",\n supportUrl: app.supportUrl || \"\",\n privacyPolicyUrl: app.privacyPolicyUrl || \"\",\n termsOfServiceUrl: app.termsOfServiceUrl || \"\",\n githubUrl: app.githubUrl || \"\",\n oauthAuthUrl: app.oauthAuthUrl || \"\",\n oauthRevokeUrl: app.oauthRevokeUrl || \"\",\n requiresApproval: app.requiresApproval,\n });\n // Load compatibleProducts from manifest.compatibility.products\n // Note: mapToApplication doesn't copy manifest, so read from raw query data directly.\n const manifest = data?.application?.manifest as\n | Record<string, unknown>\n | null\n | undefined;\n const compatibility = manifest?.compatibility as\n | Record<string, unknown>\n | undefined;\n const products = Array.isArray(compatibility?.products)\n ? (compatibility.products as string[])\n : [];\n setCompatibleProducts(products);\n setHasChanges(false);\n }\n // Re-sync form when the underlying application data changes (e.g. after\n // a refetch triggered by a mutation). We intentionally depend on\n // `data?.application` rather than the derived `app` object so that the\n // effect fires when the query cache updates.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [data?.application]);\n\n // ---------------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------------\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleChange = (\n field: keyof EditApplicationFormData,\n value: string | string[] | boolean,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n setHasChanges(true);\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const handleRotateSecret = async () => {\n if (!appId) return;\n setIsRotating(true);\n try {\n const result = await rotateSigningSecret({ variables: { id: appId } });\n const newSecret =\n result.data?.rotateApplicationSigningSecret?.signingSecret;\n if (newSecret) {\n setRotatedSecret(newSecret);\n setShowSecret(true);\n }\n } catch {\n nativeNotify(\"error\");\n } finally {\n setIsRotating(false);\n }\n };\n\n const handleCopySecret = async (secret: string) => {\n await navigator.clipboard.writeText(secret);\n setCopiedSecret(true);\n setTimeout(() => setCopiedSecret(false), 2000);\n };\n\n const validateForm = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.appNameRequired\",\n \"Application name is required\",\n );\n } else if (formData.name.length < 3) {\n newErrors.name = tr(\n \"devportal.appNameMinLength\",\n \"Name must be at least 3 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.appNameMaxLength\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalidChars\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (!formData.shortDescription.trim()) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionRequired\",\n \"Short description is required\",\n );\n } else if (formData.shortDescription.length > 200) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionMaxLength\",\n \"Short description must be less than 200 characters\",\n );\n }\n\n if (!formData.appType) {\n newErrors.appType = tr(\n \"devportal.appTypeRequired\",\n \"Application type is required\",\n );\n }\n\n if (!formData.category) {\n newErrors.category = tr(\n \"devportal.categoryRequired\",\n \"Category is required\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm() || !app) {\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n name: formData.name,\n shortDescription: formData.shortDescription,\n description: formData.description,\n appType: formData.appType,\n visibility: formData.visibility,\n category: formData.category,\n tags: formData.tags,\n keywords: formData.keywords,\n websiteUrl: formData.websiteUrl || null,\n documentationUrl: formData.documentationUrl || null,\n supportUrl: formData.supportUrl || null,\n privacyPolicyUrl: formData.privacyPolicyUrl || null,\n termsOfServiceUrl: formData.termsOfServiceUrl || null,\n githubUrl: formData.githubUrl || null,\n oauthAuthUrl: formData.oauthAuthUrl || null,\n oauthRevokeUrl: formData.oauthRevokeUrl || null,\n compatibleProducts,\n },\n },\n });\n setHasChanges(false);\n } catch (err) {\n console.error(\"Failed to update application:\", err);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleDelete = async () => {\n if (!app) return;\n void nativeImpact(\"medium\");\n try {\n await deleteApplication({\n variables: { id: app.id },\n });\n void nativeNotify(\"success\");\n handleNavigate(\"/apps\");\n } catch (err) {\n void nativeNotify(\"error\");\n console.error(\"Failed to delete application:\", err);\n }\n };\n\n const handleDeprecate = async () => {\n if (!app) return;\n try {\n // Deprecate is modelled as a status transition via update. The backend\n // treats a visibility change to PRIVATE + status mutation or a dedicated\n // deprecation endpoint. Here we use submitApplication with a status note\n // as the deprecate action since there is no separate `deprecateApplication`\n // mutation. If a dedicated mutation is added later, swap it in here.\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n visibility: \"PRIVATE\",\n },\n },\n });\n setShowDeprecateDialog(false);\n } catch (err) {\n console.error(\"Failed to deprecate application:\", err);\n }\n };\n\n const handleDeactivate = async () => {\n if (!app) return;\n try {\n await deactivateApp({\n variables: { id: app.id },\n });\n setShowDeactivateDialog(false);\n handleNavigate(\"/apps\");\n } catch (err) {\n console.error(\"Failed to deactivate application:\", err);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Loading / Error / Not-found states\n // ---------------------------------------------------------------------------\n if (loading) {\n return (\n <LoadingState\n message={tr(\"devportal.loadingApplication\", \"Loading application...\")}\n />\n );\n }\n\n if (queryError) {\n return (\n <PageLayout\n title={tr(\"devportal.error\", \"Error\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle size={48} className=\"text-status-error-text mb-4\" />\n <h3 className=\"text-lg font-semibold mb-2\">\n {tr(\n \"devportal.failedToLoadApplication\",\n \"Failed to load application\",\n )}\n </h3>\n <p className=\"text-sm text-text-secondary\">{queryError.message}</p>\n </div>\n </PageLayout>\n );\n }\n\n if (!app) {\n return (\n <PageLayout\n title={tr(\"devportal.notFound\", \"Not Found\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <EmptyState\n illustration=\"empty-generic\"\n icon={<AppWindow size={48} />}\n title={tr(\"devportal.applicationNotFound\", \"Application not found\")}\n description={tr(\n \"devportal.applicationNotFoundDescription\",\n \"The application you're looking for doesn't exist or you don't have access to it.\",\n )}\n />\n </PageLayout>\n );\n }\n\n // ---------------------------------------------------------------------------\n // Derived flags\n // ---------------------------------------------------------------------------\n const canSubmitForReview =\n app.status === \"DRAFT\" || app.status === \"REJECTED\";\n const canDeprecate = app.status === \"PUBLISHED\";\n const canDeactivate = app.status === \"ACTIVE\" || app.status === \"PUBLISHED\";\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={`${tr(\"devportal.editAppTitlePrefix\", \"Edit:\")} ${app.name}`}\n description={tr(\n \"devportal.editAppDescription\",\n \"Update your application settings\",\n )}\n purpose={tr(\n \"devportal.editAppPurpose\",\n \"Change how this app appears and behaves — its name, description, classification, links, and lifecycle settings. Use the danger zone at the bottom to deprecate or delete it when it is retired.\",\n )}\n backLink={`${basePath}/apps/${app.id}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={app.status} type=\"app\" />\n </div>\n }\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Status banner for published apps */}\n {app.status === \"PUBLISHED\" && (\n <div className=\"rounded-lg border border-border-subtle bg-status-warning-bg-subtle p-4 flex gap-3\">\n <AlertTriangle\n size={20}\n className=\"text-status-warning-text flex-shrink-0 mt-0.5\"\n />\n <div className=\"text-sm text-status-warning-text\">\n <p className=\"font-medium\">\n {tr(\n \"devportal.appIsPublished\",\n \"This application is published\",\n )}\n </p>\n <p className=\"mt-1 text-status-warning-text\">\n {tr(\n \"devportal.publishedAppChangesWarning\",\n \"Changes to published apps may require re-review. Major changes will put the app back into review status.\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {/* Basic Information */}\n <PageSection\n title={tr(\"devportal.basicInformation\", \"Basic Information\")}\n description={tr(\n \"devportal.basicInformationDescription\",\n \"Core details about your application\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.applicationName\", \"Application Name\")}\n value={formData.name}\n onChange={(e) => handleChange(\"name\", e.target.value)}\n placeholder={tr(\n \"devportal.applicationNamePlaceholder\",\n \"My Awesome App\",\n )}\n required\n error={errors.name}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.slugPlaceholder\", \"my-awesome-app\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugChangeWarning\",\n \"Changing the slug may break existing integrations\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.shortDescription\", \"Short Description\")}\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder={tr(\n \"devportal.shortDescriptionPlaceholder\",\n \"A brief description of what your app does\",\n )}\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 ${tr(\"devportal.characters\", \"characters\")}`}\n rows={2}\n />\n\n <MarkdownEditor\n label={tr(\"devportal.fullDescription\", \"Full Description\")}\n value={formData.description}\n onChange={(value) => handleChange(\"description\", value)}\n placeholder={tr(\n \"devportal.fullDescriptionPlaceholder\",\n \"Detailed description of your application, features, and use cases...\",\n )}\n rows={6}\n hint={tr(\n \"devportal.markdownSupported\",\n \"Markdown formatting is supported\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Classification */}\n <PageSection\n title={tr(\"devportal.classification\", \"Classification\")}\n description={tr(\n \"devportal.classificationDescription\",\n \"Categorize your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label={tr(\"devportal.applicationType\", \"Application Type\")}\n value={formData.appType}\n onChange={(e) => handleChange(\"appType\", e.target.value)}\n options={APP_TYPE_OPTIONS}\n required\n error={errors.appType}\n />\n\n <SelectField\n label={tr(\"devportal.category\", \"Category\")}\n value={formData.category}\n onChange={(e) => handleChange(\"category\", e.target.value)}\n options={CATEGORY_OPTIONS}\n required\n error={errors.category}\n />\n\n <SelectField\n label={tr(\"devportal.visibility\", \"Visibility\")}\n value={formData.visibility}\n onChange={(e) =>\n handleChange(\"visibility\", e.target.value as AppVisibility)\n }\n options={\n app?.appType === \"WORKFLOW\"\n ? WORKFLOW_VISIBILITY_OPTIONS\n : BASE_VISIBILITY_OPTIONS\n }\n hint={\n formData.visibility === \"PUBLIC\"\n ? tr(\n \"devportal.visibilityHintPublic\",\n \"Listed in the marketplace after review approval — anyone can discover and install it\",\n )\n : formData.visibility === \"UNLISTED\"\n ? tr(\n \"devportal.visibilityHintUnlisted\",\n \"Not in the store, but accessible via a direct link. You can still submit for marketplace review\",\n )\n : formData.visibility === \"INTERNAL\"\n ? tr(\n \"devportal.visibilityHintInternal\",\n \"Visible to all members of your developer organization — no one outside can access it\",\n )\n : tr(\n \"devportal.visibilityHintPrivate\",\n \"Only you (the creator) can see this — other org members cannot access it\",\n )\n }\n />\n </div>\n\n <TagInput\n label={tr(\"devportal.tags\", \"Tags\")}\n value={formData.tags}\n onChange={(tags) => handleChange(\"tags\", tags)}\n placeholder={tr(\n \"devportal.addTagsPlaceholder\",\n \"Add tags (press Enter)\",\n )}\n />\n\n <TagInput\n label={tr(\"devportal.keywords\", \"Keywords\")}\n value={formData.keywords}\n onChange={(keywords) => handleChange(\"keywords\", keywords)}\n placeholder={tr(\n \"devportal.addKeywordsPlaceholder\",\n \"Add keywords (press Enter)\",\n )}\n />\n\n {/* Compatible Products — NODE and WORKFLOW only */}\n {(formData.appType === \"NODE\" ||\n formData.appType === \"WORKFLOW\") && (\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Compatible Products\n </label>\n <p className=\"text-xs text-text-secondary\">\n Select which products this {formData.appType.toLowerCase()}{\" \"}\n works with. Users will see it in those products&apos; node\n stores.\n </p>\n <div className=\"flex flex-wrap gap-3\">\n {COMPATIBLE_PRODUCTS.map((product) => {\n const checked = compatibleProducts.includes(product.value);\n return (\n <label\n key={product.value}\n className={cn(\n \"flex items-center gap-2 cursor-pointer rounded-md border px-3 py-2 text-sm transition-colors\",\n checked\n ? \"border-primary bg-primary/10 text-text-primary\"\n : \"border-border-subtle hover:border-border text-text-secondary hover:text-text-primary\",\n )}\n >\n <input\n type=\"checkbox\"\n className=\"sr-only\"\n checked={checked}\n onChange={() => {\n setCompatibleProducts((prev) =>\n checked\n ? prev.filter((v) => v !== product.value)\n : [...prev, product.value],\n );\n setHasChanges(true);\n }}\n />\n {checked && (\n <Check\n size={14}\n className=\"text-primary flex-shrink-0\"\n />\n )}\n {product.label}\n </label>\n );\n })}\n </div>\n </div>\n )}\n </PageSection>\n\n {/* URLs */}\n <PageSection\n title={tr(\"devportal.links\", \"Links\")}\n description={tr(\n \"devportal.linksDescription\",\n \"External resources for your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.websiteUrlPlaceholder\",\n \"https://example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.documentationUrl\", \"Documentation URL\")}\n type=\"url\"\n value={formData.documentationUrl}\n onChange={(e) =>\n handleChange(\"documentationUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.documentationUrlPlaceholder\",\n \"https://docs.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.supportUrl\", \"Support URL\")}\n type=\"url\"\n value={formData.supportUrl}\n onChange={(e) => handleChange(\"supportUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.supportUrlPlaceholder\",\n \"https://support.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.githubUrl\", \"GitHub URL\")}\n type=\"url\"\n value={formData.githubUrl}\n onChange={(e) => handleChange(\"githubUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.githubUrlPlaceholder\",\n \"https://github.com/org/repo\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.privacyPolicyUrl\", \"Privacy Policy URL\")}\n type=\"url\"\n value={formData.privacyPolicyUrl}\n onChange={(e) =>\n handleChange(\"privacyPolicyUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.privacyPolicyUrlPlaceholder\",\n \"https://example.com/privacy\",\n )}\n />\n\n <InputField\n label={tr(\n \"devportal.termsOfServiceUrl\",\n \"Terms of Service URL\",\n )}\n type=\"url\"\n value={formData.termsOfServiceUrl}\n onChange={(e) =>\n handleChange(\"termsOfServiceUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.termsOfServiceUrlPlaceholder\",\n \"https://example.com/terms\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Integration Setup — INTEGRATION / API_INTEGRATION only */}\n {(formData.appType === \"INTEGRATION\" ||\n formData.appType === \"API_INTEGRATION\") && (\n <PageSection\n title=\"Integration Setup\"\n description=\"Configure the endpoints for your integration. After a user installs, we redirect them to your callback URL so your server can complete the connection.\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Post-install Callback URL\"\n type=\"url\"\n value={formData.oauthAuthUrl}\n onChange={(e) => handleChange(\"oauthAuthUrl\", e.target.value)}\n placeholder=\"https://yourapp.com/callback\"\n hint=\"Users land here after install with ?installation_id=xxx appended\"\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=\"We POST to this URL when a user uninstalls your integration\"\n />\n {/* Signing secret — read-only, rotate-only */}\n {(() => {\n const displaySecret =\n rotatedSecret ?? data?.application?.signingSecret;\n const masked = \"boff_sk_\" + \"•\".repeat(32);\n return (\n <div className=\"space-y-1.5\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Signing Secret\n </label>\n <p className=\"text-xs text-text-secondary\">\n Use this secret to authenticate{\" \"}\n <code className=\"font-mono text-xs bg-bg-sunken px-1 py-0.5 rounded\">\n verifyInstallation\n </code>{\" \"}\n calls from your integration server. Store it securely —\n it is only shown immediately after generation or\n rotation.\n </p>\n {displaySecret ? (\n <div className=\"flex items-center gap-2\">\n <div className=\"flex-1 flex items-center gap-0 rounded-md border border-border-default bg-bg-sunken overflow-hidden\">\n <code className=\"flex-1 px-3 py-2 text-xs font-mono text-text-primary truncate\">\n {showSecret ? displaySecret : masked}\n </code>\n <button\n type=\"button\"\n onClick={() => setShowSecret((v) => !v)}\n className=\"px-2 py-2 text-text-secondary hover:text-text-primary border-l border-border-default\"\n title={showSecret ? \"Hide\" : \"Reveal\"}\n >\n {showSecret ? (\n <EyeOff size={14} />\n ) : (\n <Eye size={14} />\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => handleCopySecret(displaySecret)}\n className=\"px-2 py-2 text-text-secondary hover:text-text-primary border-l border-border-default\"\n title=\"Copy to clipboard\"\n >\n {copiedSecret ? (\n <Check size={14} className=\"text-green-500\" />\n ) : (\n <Copy size={14} />\n )}\n </button>\n </div>\n <button\n type=\"button\"\n onClick={handleRotateSecret}\n disabled={isRotating}\n className=\"flex items-center gap-1.5 px-3 py-2 text-xs rounded-md border border-border-default bg-bg-default text-text-primary hover:bg-bg-subtle disabled:opacity-50\"\n title=\"Rotate signing secret\"\n >\n <RefreshCw\n size={12}\n className={isRotating ? \"animate-spin\" : \"\"}\n />\n Rotate\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={handleRotateSecret}\n disabled={isRotating}\n className=\"flex items-center gap-1.5 px-3 py-2 text-xs rounded-md border border-border-default bg-bg-default text-text-primary hover:bg-bg-subtle disabled:opacity-50\"\n >\n <RefreshCw\n size={12}\n className={isRotating ? \"animate-spin\" : \"\"}\n />\n Generate Signing Secret\n </button>\n )}\n {rotatedSecret && (\n <p className=\"text-xs text-amber-600 dark:text-amber-400\">\n New secret generated. Copy it now — the old secret is\n already invalidated.\n </p>\n )}\n </div>\n );\n })()}\n </div>\n </PageSection>\n )}\n\n {/* Settings */}\n <PageSection\n title={tr(\"devportal.settings\", \"Settings\")}\n description={tr(\n \"devportal.settingsDescription\",\n \"Additional configuration options\",\n )}\n >\n <div className=\"space-y-4\">\n <CheckboxField\n label={tr(\n \"devportal.requireWorkspaceAdminApproval\",\n \"Require workspace admin approval\",\n )}\n description={tr(\n \"devportal.requireWorkspaceAdminApprovalDescription\",\n \"When enabled, workspace admins must approve installations before users can access this app\",\n )}\n checked={formData.requiresApproval}\n onChange={(e) =>\n handleChange(\"requiresApproval\", e.target.checked)\n }\n />\n </div>\n </PageSection>\n\n {/* Submission readiness checklist */}\n {canSubmitForReview && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken p-4 space-y-2\">\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <Send size={14} />\n {tr(\"devportal.submissionChecklist\", \"Submission Checklist\")}\n </p>\n {[\n {\n label: tr(\"devportal.checkName\", \"Name set\"),\n ok: !!formData.name.trim(),\n },\n {\n label: tr(\n \"devportal.checkShortDesc\",\n \"Short description set\",\n ),\n ok: !!formData.shortDescription.trim(),\n },\n {\n label: tr(\n \"devportal.checkFullDesc\",\n \"Full description added\",\n ),\n ok: !!formData.description.trim(),\n },\n {\n label: tr(\"devportal.checkCategory\", \"Category selected\"),\n ok: !!formData.category,\n },\n {\n label: tr(\n \"devportal.checkVisibility\",\n \"Visibility set to Public or Unlisted\",\n ),\n ok:\n !!formData.visibility && formData.visibility !== \"PRIVATE\",\n },\n {\n label: tr(\"devportal.checkSaved\", \"All changes saved\"),\n ok: !hasChanges,\n },\n ...(app?.appType === \"NODE\" || app?.appType === \"WORKFLOW\"\n ? [\n {\n label: tr(\n \"devportal.checkVersion\",\n \"At least one version uploaded\",\n ),\n ok: (data?.application?.versions?.totalCount ?? 0) > 0,\n },\n ]\n : []),\n ].map((item) => (\n <div\n key={item.label}\n className=\"flex items-center gap-2 text-sm\"\n >\n {item.ok ? (\n <span className=\"text-status-success-text\">✓</span>\n ) : (\n <span className=\"text-status-error-text\">✗</span>\n )}\n <span\n className={\n item.ok\n ? \"text-text-secondary line-through\"\n : \"text-text-primary\"\n }\n >\n {item.label}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col gap-4 border-t pt-6\">\n {/* Primary actions */}\n <div className=\"flex items-center justify-end\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !hasChanges || !canUpdate}\n aria-disabled={!canUpdate}\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 <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.saving\", \"Saving...\")}\n </>\n ) : (\n <>\n <Save size={16} />\n {tr(\"devportal.saveChanges\", \"Save Changes\")}\n </>\n )}\n </button>\n </div>\n\n {/* Danger zone */}\n <div className=\"rounded-lg border border-border-subtle bg-status-error-bg-subtle p-4\">\n <h3 className=\"text-sm font-medium text-status-error-text\">\n {tr(\"devportal.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-error-text\">\n {tr(\n \"devportal.dangerZoneWarning\",\n \"These actions are irreversible. Please proceed with caution.\",\n )}\n </p>\n <div className=\"mt-4 flex gap-3\">\n {canDeprecate && (\n <button\n type=\"button\"\n onClick={() => setShowDeprecateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Archive size={14} />\n {tr(\"devportal.deprecateApp\", \"Deprecate App\")}\n </button>\n )}\n {canDeactivate && (\n <button\n type=\"button\"\n onClick={() => setShowDeactivateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Power size={14} />\n {tr(\"devportal.deactivateApp\", \"Deactivate App\")}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setShowDeleteDialog(true)}\n disabled={!canDelete}\n aria-disabled={!canDelete}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg bg-bg-surface px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n <Trash2 size={14} />\n {tr(\"devportal.deleteApp\", \"Delete App\")}\n </button>\n </div>\n </div>\n </div>\n </form>\n\n {/* Delete confirmation dialog */}\n <ConfirmDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n title={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n description={tr(\n \"devportal.deleteApplicationConfirm\",\n `Are you sure you want to delete \"${app.name}\"? This action cannot be undone. All OAuth clients, API keys, webhooks, and analytics data will be permanently deleted.`,\n )}\n confirmLabel={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n variant=\"destructive\"\n onConfirm={handleDelete}\n />\n\n {/* Deprecate confirmation dialog */}\n <ConfirmDialog\n open={showDeprecateDialog}\n onOpenChange={setShowDeprecateDialog}\n title={tr(\"devportal.deprecateApplication\", \"Deprecate Application\")}\n description={tr(\n \"devportal.deprecateApplicationConfirm\",\n `Are you sure you want to deprecate \"${app.name}\"? Existing users will still be able to use the app, but it will no longer be visible in the marketplace and new installations will be disabled.`,\n )}\n confirmLabel={tr(\n \"devportal.deprecateApplication\",\n \"Deprecate Application\",\n )}\n variant=\"warning\"\n onConfirm={handleDeprecate}\n />\n\n {/* Deactivate confirmation dialog */}\n <ConfirmDialog\n open={showDeactivateDialog}\n onOpenChange={setShowDeactivateDialog}\n title={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n description={tr(\n \"devportal.deactivateApplicationConfirm\",\n `Are you sure you want to deactivate \"${app.name}\"? This will revoke all active tokens and disable the app from the auth-svc layer. The app will no longer be able to authenticate or act on behalf of users.`,\n )}\n confirmLabel={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n variant=\"destructive\"\n onConfirm={handleDeactivate}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}