@burdenoff/microfe-devportal 2026.720.3 → 2026.720.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/hooks/usePackageUpload.d.ts.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.d.ts.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +4 -5
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +1 -1
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/PublishPage.d.ts.map +1 -1
- package/dist/devportal/pages/PublishPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
- package/dist/devportal/types/index.d.ts.map +1 -1
- package/dist/devportal/types/index.js.map +1 -1
- package/dist/generated/global-operations.d.ts +30 -0
- package/dist/generated/global-operations.d.ts.map +1 -1
- package/dist/generated/global-operations.js +222 -210
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.d.ts +5 -0
- package/dist/generated/global-types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useDevportal as e } from "../providers/DevportalProvider.js";
|
|
2
2
|
import { EmptyState as t } from "../components/EmptyState.js";
|
|
3
|
-
import {
|
|
3
|
+
import { useDeactivateApplicationMutation as n, useGetApplicationsQuery as r } from "../../generated/global-operations.js";
|
|
4
4
|
import { getErrorMessage as i } from "../hooks/useGraphQL.js";
|
|
5
5
|
import { PageLayout as a } from "../components/PageLayout.js";
|
|
6
6
|
import { APP_STATUS_COLORS as o, APP_TYPE_LABELS as s } from "../utils/constants.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApplicationsPage.js","names":[],"sources":["../../../src/devportal/pages/ApplicationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n AppWindow,\n Loader2,\n AlertCircle,\n Archive,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { AppCard } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, AppStatus, AppType } from \"../types\";\nimport { APP_TYPE_LABELS, APP_STATUS_COLORS } from \"../utils/constants\";\nimport {\n useGetApplicationsQuery,\n useDeactivateAppMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\ntype BulkAction = \"deactivate\" | null;\n\nfunction formatBulkResult(\n action: \"Deactivated\",\n succeeded: number,\n failed: number,\n): string {\n return `${action} ${succeeded} application${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`;\n}\n\nfunction StatusBadge({ status }: { status: AppStatus }) {\n const colors: Record<string, string> = {\n ACTIVE: \"bg-status-success-bg-subtle text-status-success-text\",\n PUBLISHED: \"bg-status-success-bg-subtle text-status-success-text\",\n DRAFT: \"bg-bg-sunken text-text-secondary\",\n SUBMITTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n IN_REVIEW: \"bg-status-warning-bg-subtle text-status-warning-text\",\n APPROVED: \"bg-status-success-bg-subtle text-status-success-text\",\n UNLISTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n REJECTED: \"bg-status-error-bg-subtle text-status-error-text\",\n SUSPENDED: \"bg-status-error-bg-subtle text-status-error-text\",\n INACTIVE: \"bg-bg-sunken text-text-muted\",\n REVOKED: \"bg-status-error-bg-subtle text-status-error-text\",\n DEPRECATED: \"bg-bg-sunken text-text-muted\",\n };\n return (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${colors[status] ?? \"bg-bg-sunken text-text-secondary\"}`}\n >\n {status === \"INACTIVE\" || status === \"REVOKED\"\n ? \"Deactivated\"\n : status.replace(/_/g, \" \")}\n </span>\n );\n}\n\n/**\n * Map GraphQL application to local Application type\n */\nfunction mapApplication(app: ApplicationFieldsFragment): Application {\n // Convert screenshots array to Record if needed\n const screenshots = Array.isArray(app.screenshots)\n ? app.screenshots.length > 0\n ? (app.screenshots as unknown as Record<string, unknown>)\n : undefined\n : (app.screenshots as Record<string, unknown> | undefined);\n\n // Convert pricingDetails\n const pricingDetails =\n app.pricingDetails && typeof app.pricingDetails === \"object\"\n ? (app.pricingDetails as Record<string, unknown>)\n : undefined;\n\n return {\n id: app.id,\n name: app.name,\n slug: app.slug,\n description: app.description ?? undefined,\n shortDescription: app.shortDescription ?? undefined,\n appType: app.appType as AppType,\n status: app.status as AppStatus,\n visibility: app.visibility as \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\",\n category: app.category ?? undefined,\n tags: app.tags ?? [],\n keywords: app.keywords ?? [],\n iconUrl: app.iconUrl ?? undefined,\n bannerUrl: app.bannerUrl ?? undefined,\n screenshots,\n videoUrl: app.videoUrl ?? undefined,\n websiteUrl: app.websiteUrl ?? undefined,\n documentationUrl: app.documentationUrl ?? undefined,\n supportUrl: app.supportUrl ?? undefined,\n privacyPolicyUrl: app.privacyPolicyUrl ?? undefined,\n termsOfServiceUrl: app.termsOfServiceUrl ?? undefined,\n githubUrl: app.githubUrl ?? undefined,\n pricingType: app.pricingType ?? undefined,\n pricingDetails,\n isVerified: app.isVerified,\n isFeatured: app.isFeatured,\n averageRating: app.averageRating ?? undefined,\n reviewCount: app.reviewCount ?? 0,\n installCount: app.installCount ?? 0,\n activeInstallCount: app.activeInstallCount ?? 0,\n requiresApproval: app.requiresApproval,\n reviewStatus: app.reviewStatus ?? undefined,\n reviewNotes: app.reviewNotes ?? undefined,\n reviewedBy: app.reviewedBy ?? undefined,\n lastReviewedAt: app.lastReviewedAt ?? undefined,\n submittedAt: app.submittedAt ?? undefined,\n publishedAt: app.publishedAt ?? undefined,\n developerId: app.developerId,\n developerOrganizationId: app.developerOrganizationId ?? undefined,\n tenantId: app.tenantId,\n createdAt: app.createdAt,\n updatedAt: app.updatedAt,\n };\n}\n\n/**\n * Applications listing page\n *\n * Displays all applications for the current developer.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const ApplicationsPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const createPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_CREATE);\n const updatePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_UPDATE);\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"list\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<AppStatus | \"\">(\"\");\n const [typeFilter, setTypeFilter] = useState<AppType | \"\">(\"\");\n const [selectedAppIds, setSelectedAppIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<BulkAction>(null);\n const [feedback, setFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n\n // Fetch applications from GraphQL API — always scope to active org so\n // totalCount from the server matches what the user actually owns.\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: {\n limit: 50,\n offset: 0,\n status: statusFilter || undefined,\n organizationId: activeOrgId ?? undefined,\n },\n fetchPolicy: \"cache-and-network\",\n });\n\n const applications: Application[] = useMemo(\n () =>\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) =>\n mapApplication(edge.node),\n ) ?? [],\n [data?.applications?.edges],\n );\n\n const [deactivateApp] = useDeactivateAppMutation();\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Client-side filtering: org scope + search + type (status filtered server-side)\n const filteredApps = applications.filter((app: Application) => {\n const matchesOrg =\n !activeOrgId || app.developerOrganizationId === activeOrgId;\n const matchesSearch =\n !searchQuery ||\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n app.shortDescription?.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesType = !typeFilter || app.appType === typeFilter;\n return matchesOrg && matchesSearch && matchesType;\n });\n\n const appStats = useMemo(() => {\n // Use filteredApps (org-scoped) for all counts so stats match what's shown.\n const total = data?.applications?.totalCount ?? filteredApps.length;\n const active = filteredApps.filter(\n (app: Application) => app.status === \"ACTIVE\",\n ).length;\n const draft = filteredApps.filter(\n (app: Application) => app.status === \"DRAFT\",\n ).length;\n const published = filteredApps.filter(\n (app: Application) => app.status === \"PUBLISHED\",\n ).length;\n return { total, active, draft, published };\n }, [filteredApps, data?.applications?.totalCount]);\n\n const selectedApps = useMemo(\n () => applications.filter((app: Application) => selectedAppIds.has(app.id)),\n [applications, selectedAppIds],\n );\n\n const deactivatableApps = useMemo(\n () =>\n selectedApps.filter(\n (app: Application) =>\n app.status !== \"INACTIVE\" && app.status !== \"REVOKED\",\n ),\n [selectedApps],\n );\n\n const allVisibleSelected =\n filteredApps.length > 0 &&\n filteredApps.every((app: Application) => selectedAppIds.has(app.id));\n const partiallySelected =\n filteredApps.some((app: Application) => selectedAppIds.has(app.id)) &&\n !allVisibleSelected;\n\n const toggleAppSelection = (appId: string, checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(appId);\n } else {\n next.delete(appId);\n }\n return next;\n });\n };\n\n const toggleAllVisible = (checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n filteredApps.forEach((app: Application) => {\n if (checked) {\n next.add(app.id);\n } else {\n next.delete(app.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedAppIds(new Set());\n\n const runBulkDeactivate = async () => {\n if (deactivatableApps.length === 0) return;\n\n const results = await Promise.allSettled(\n deactivatableApps.map((app: Application) =>\n deactivateApp({ variables: { id: app.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result: PromiseSettledResult<unknown>) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: formatBulkResult(\"Deactivated\", succeeded, failed),\n });\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingApplications\", \"Loading applications...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\n \"devportal.failedToLoadApplications\",\n \"Failed to load applications\",\n )}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n purpose={tr(\n \"devportal.applicationsPurpose\",\n \"Applications are the integrations you build on the platform — OAuth apps, store listings, and API consumers. Create one here to get client credentials, request scopes, and ship to the marketplace.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n disabled={!createPerm.allowed}\n aria-disabled={!createPerm.allowed}\n title={\n !createPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchApplications\",\n \"Search applications...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as AppStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">\n {tr(\"devportal.allStatus\", \"All Status\")}\n </option>\n {Object.keys(APP_STATUS_COLORS).map((status) => (\n <option key={status} value={status}>\n {status.replace(/_/g, \" \")}\n </option>\n ))}\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as AppType | \"\")}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">{tr(\"devportal.allTypes\", \"All Types\")}</option>\n {Object.entries(APP_TYPE_LABELS).map(([value, label]) => (\n <option key={value} value={value}>\n {label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 rounded-md border p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 ${viewMode === \"grid\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title=\"Grid view\"\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 ${viewMode === \"list\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title=\"Table view\"\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel className=\"mt-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.active\", \"Active\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.published}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.draft\", \"Draft\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.draft}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {feedback && (\n <div\n className={`rounded-lg border px-4 py-3 text-sm ${\n feedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-status-error-border/40 bg-status-error-bg/10 text-status-error-text\"\n }`}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{feedback.message}</span>\n <button\n onClick={() => setFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {filteredApps.length > 0 && (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-pop)]\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel=\"Select all visible applications\"\n />\n <span className=\"text-sm font-medium\">\n Select all visible\n </span>\n </div>\n {selectedAppIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedAppIds.size} selected\n </span>\n <button\n onClick={() => setBulkAction(\"deactivate\")}\n disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n aria-disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n title={\n !updatePerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md border border-status-warning-bg px-3 py-2 text-sm font-medium text-status-warning-text transition-colors hover:bg-status-warning-bg-subtle disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Archive size={16} aria-hidden=\"true\" />\n {tr(\"devportal.deactivateSelected\", \"Deactivate Selected\")}\n {deactivatableApps.length > 0\n ? ` (${deactivatableApps.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredApps.length} application\n {filteredApps.length !== 1 ? \"s\" : \"\"} found\n </p>\n\n {/* Applications */}\n {filteredApps.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredApps.map((app: Application) => (\n <AppCard\n key={app.id}\n app={app}\n selected={selectedAppIds.has(app.id)}\n onToggleSelect={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n />\n ))}\n </div>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken text-left\">\n <th className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel=\"Select all\"\n />\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Name\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Type\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Created\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\"></th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {filteredApps.map((app: Application) => (\n <tr\n key={app.id}\n className={`hover:bg-bg-surface-hover transition-colors ${selectedAppIds.has(app.id) ? \"bg-action-primary-bg/5\" : \"\"}`}\n >\n <td className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={selectedAppIds.has(app.id)}\n onChange={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n ariaLabel={`Select ${app.name}`}\n />\n </td>\n <td className=\"px-4 py-3\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"group flex items-center gap-3 text-left\"\n >\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt=\"\"\n className=\"h-8 w-8 rounded object-cover\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded bg-bg-sunken text-text-secondary\">\n <AppWindow size={14} />\n </div>\n )}\n <div>\n <p className=\"font-medium text-text-primary group-hover:text-action-primary-text transition-colors\">\n {app.name}\n </p>\n {app.shortDescription && (\n <p className=\"text-xs text-text-secondary line-clamp-1\">\n {app.shortDescription}\n </p>\n )}\n </div>\n </button>\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge status={app.status} />\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {APP_TYPE_LABELS[app.appType] || app.appType}\n </td>\n <td className=\"px-4 py-3 text-text-secondary tabular-nums\">\n {new Date(app.createdAt).toLocaleDateString()}\n </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"rounded-md px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors\"\n >\n View\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n ) : (\n <EmptyState\n illustration={\n searchQuery || statusFilter || typeFilter\n ? \"empty-search\"\n : \"empty-data\"\n }\n icon={<AppWindow size={32} />}\n title={tr(\"devportal.noApplicationsFound\", \"No applications found\")}\n description={\n searchQuery || statusFilter || typeFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstApp\",\n \"Create your first application to get started\",\n )\n }\n action={\n !searchQuery && !statusFilter && !typeFilter ? (\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n <ConfirmDialog\n open={bulkAction === \"deactivate\"}\n onOpenChange={(open) => {\n if (!open) setBulkAction(null);\n }}\n onConfirm={runBulkDeactivate}\n title=\"Deactivate selected applications\"\n description={`Deactivate ${deactivatableApps.length} selected application${deactivatableApps.length === 1 ? \"\" : \"s\"}? Users and integrations may lose access until these apps are re-enabled.`}\n confirmLabel=\"Deactivate\"\n cancelLabel=\"Cancel\"\n variant=\"warning\"\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ApplicationsPage.js","names":[],"sources":["../../../src/devportal/pages/ApplicationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n AppWindow,\n Loader2,\n AlertCircle,\n Archive,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { AppCard } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, AppStatus, AppType } from \"../types\";\nimport { APP_TYPE_LABELS, APP_STATUS_COLORS } from \"../utils/constants\";\nimport {\n useGetApplicationsQuery,\n useDeactivateApplicationMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\ntype BulkAction = \"deactivate\" | null;\n\nfunction formatBulkResult(\n action: \"Deactivated\",\n succeeded: number,\n failed: number,\n): string {\n return `${action} ${succeeded} application${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`;\n}\n\nfunction StatusBadge({ status }: { status: AppStatus }) {\n const colors: Record<string, string> = {\n ACTIVE: \"bg-status-success-bg-subtle text-status-success-text\",\n PUBLISHED: \"bg-status-success-bg-subtle text-status-success-text\",\n DRAFT: \"bg-bg-sunken text-text-secondary\",\n SUBMITTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n IN_REVIEW: \"bg-status-warning-bg-subtle text-status-warning-text\",\n APPROVED: \"bg-status-success-bg-subtle text-status-success-text\",\n UNLISTED: \"bg-status-warning-bg-subtle text-status-warning-text\",\n REJECTED: \"bg-status-error-bg-subtle text-status-error-text\",\n SUSPENDED: \"bg-status-error-bg-subtle text-status-error-text\",\n INACTIVE: \"bg-bg-sunken text-text-muted\",\n REVOKED: \"bg-status-error-bg-subtle text-status-error-text\",\n DEPRECATED: \"bg-bg-sunken text-text-muted\",\n };\n return (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${colors[status] ?? \"bg-bg-sunken text-text-secondary\"}`}\n >\n {status === \"INACTIVE\" || status === \"REVOKED\"\n ? \"Deactivated\"\n : status.replace(/_/g, \" \")}\n </span>\n );\n}\n\n/**\n * Map GraphQL application to local Application type\n */\nfunction mapApplication(app: ApplicationFieldsFragment): Application {\n // Convert screenshots array to Record if needed\n const screenshots = Array.isArray(app.screenshots)\n ? app.screenshots.length > 0\n ? (app.screenshots as unknown as Record<string, unknown>)\n : undefined\n : (app.screenshots as Record<string, unknown> | undefined);\n\n // Convert pricingDetails\n const pricingDetails =\n app.pricingDetails && typeof app.pricingDetails === \"object\"\n ? (app.pricingDetails as Record<string, unknown>)\n : undefined;\n\n return {\n id: app.id,\n name: app.name,\n slug: app.slug,\n description: app.description ?? undefined,\n shortDescription: app.shortDescription ?? undefined,\n appType: app.appType as AppType,\n status: app.status as AppStatus,\n visibility: app.visibility as \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\",\n category: app.category ?? undefined,\n tags: app.tags ?? [],\n keywords: app.keywords ?? [],\n iconUrl: app.iconUrl ?? undefined,\n bannerUrl: app.bannerUrl ?? undefined,\n screenshots,\n videoUrl: app.videoUrl ?? undefined,\n websiteUrl: app.websiteUrl ?? undefined,\n documentationUrl: app.documentationUrl ?? undefined,\n supportUrl: app.supportUrl ?? undefined,\n privacyPolicyUrl: app.privacyPolicyUrl ?? undefined,\n termsOfServiceUrl: app.termsOfServiceUrl ?? undefined,\n githubUrl: app.githubUrl ?? undefined,\n pricingType: app.pricingType ?? undefined,\n pricingDetails,\n isVerified: app.isVerified,\n isFeatured: app.isFeatured,\n averageRating: app.averageRating ?? undefined,\n reviewCount: app.reviewCount ?? 0,\n installCount: app.installCount ?? 0,\n activeInstallCount: app.activeInstallCount ?? 0,\n requiresApproval: app.requiresApproval,\n reviewStatus: app.reviewStatus ?? undefined,\n reviewNotes: app.reviewNotes ?? undefined,\n reviewedBy: app.reviewedBy ?? undefined,\n lastReviewedAt: app.lastReviewedAt ?? undefined,\n submittedAt: app.submittedAt ?? undefined,\n publishedAt: app.publishedAt ?? undefined,\n developerId: app.developerId,\n developerOrganizationId: app.developerOrganizationId ?? undefined,\n tenantId: app.tenantId,\n createdAt: app.createdAt,\n updatedAt: app.updatedAt,\n };\n}\n\n/**\n * Applications listing page\n *\n * Displays all applications for the current developer.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const ApplicationsPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const createPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_CREATE);\n const updatePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_UPDATE);\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"list\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<AppStatus | \"\">(\"\");\n const [typeFilter, setTypeFilter] = useState<AppType | \"\">(\"\");\n const [selectedAppIds, setSelectedAppIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<BulkAction>(null);\n const [feedback, setFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n\n // Fetch applications from GraphQL API — always scope to active org so\n // totalCount from the server matches what the user actually owns.\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: {\n limit: 50,\n offset: 0,\n status: statusFilter || undefined,\n organizationId: activeOrgId ?? undefined,\n },\n fetchPolicy: \"cache-and-network\",\n });\n\n const applications: Application[] = useMemo(\n () =>\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) =>\n mapApplication(edge.node),\n ) ?? [],\n [data?.applications?.edges],\n );\n\n const [deactivateApp] = useDeactivateApplicationMutation();\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Client-side filtering: org scope + search + type (status filtered server-side)\n const filteredApps = applications.filter((app: Application) => {\n const matchesOrg =\n !activeOrgId || app.developerOrganizationId === activeOrgId;\n const matchesSearch =\n !searchQuery ||\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n app.shortDescription?.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesType = !typeFilter || app.appType === typeFilter;\n return matchesOrg && matchesSearch && matchesType;\n });\n\n const appStats = useMemo(() => {\n // Use filteredApps (org-scoped) for all counts so stats match what's shown.\n const total = data?.applications?.totalCount ?? filteredApps.length;\n const active = filteredApps.filter(\n (app: Application) => app.status === \"ACTIVE\",\n ).length;\n const draft = filteredApps.filter(\n (app: Application) => app.status === \"DRAFT\",\n ).length;\n const published = filteredApps.filter(\n (app: Application) => app.status === \"PUBLISHED\",\n ).length;\n return { total, active, draft, published };\n }, [filteredApps, data?.applications?.totalCount]);\n\n const selectedApps = useMemo(\n () => applications.filter((app: Application) => selectedAppIds.has(app.id)),\n [applications, selectedAppIds],\n );\n\n const deactivatableApps = useMemo(\n () =>\n selectedApps.filter(\n (app: Application) =>\n app.status !== \"INACTIVE\" && app.status !== \"REVOKED\",\n ),\n [selectedApps],\n );\n\n const allVisibleSelected =\n filteredApps.length > 0 &&\n filteredApps.every((app: Application) => selectedAppIds.has(app.id));\n const partiallySelected =\n filteredApps.some((app: Application) => selectedAppIds.has(app.id)) &&\n !allVisibleSelected;\n\n const toggleAppSelection = (appId: string, checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(appId);\n } else {\n next.delete(appId);\n }\n return next;\n });\n };\n\n const toggleAllVisible = (checked: boolean) => {\n setSelectedAppIds((prev) => {\n const next = new Set(prev);\n filteredApps.forEach((app: Application) => {\n if (checked) {\n next.add(app.id);\n } else {\n next.delete(app.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedAppIds(new Set());\n\n const runBulkDeactivate = async () => {\n if (deactivatableApps.length === 0) return;\n\n const results = await Promise.allSettled(\n deactivatableApps.map((app: Application) =>\n deactivateApp({ variables: { id: app.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result: PromiseSettledResult<unknown>) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: formatBulkResult(\"Deactivated\", succeeded, failed),\n });\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingApplications\", \"Loading applications...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\n \"devportal.failedToLoadApplications\",\n \"Failed to load applications\",\n )}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myApplications\", \"My Applications\")}\n description={tr(\n \"devportal.manageYourApps\",\n \"Manage your developer applications\",\n )}\n purpose={tr(\n \"devportal.applicationsPurpose\",\n \"Applications are the integrations you build on the platform — OAuth apps, store listings, and API consumers. Create one here to get client credentials, request scopes, and ship to the marketplace.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n disabled={!createPerm.allowed}\n aria-disabled={!createPerm.allowed}\n title={\n !createPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchApplications\",\n \"Search applications...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as AppStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">\n {tr(\"devportal.allStatus\", \"All Status\")}\n </option>\n {Object.keys(APP_STATUS_COLORS).map((status) => (\n <option key={status} value={status}>\n {status.replace(/_/g, \" \")}\n </option>\n ))}\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value as AppType | \"\")}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">{tr(\"devportal.allTypes\", \"All Types\")}</option>\n {Object.entries(APP_TYPE_LABELS).map(([value, label]) => (\n <option key={value} value={value}>\n {label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 rounded-md border p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 ${viewMode === \"grid\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title=\"Grid view\"\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 ${viewMode === \"list\" ? \"bg-bg-sunken\" : \"hover:bg-bg-sunken/50\"}`}\n title=\"Table view\"\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel className=\"mt-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.active\", \"Active\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.published}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.draft\", \"Draft\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {appStats.draft}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {feedback && (\n <div\n className={`rounded-lg border px-4 py-3 text-sm ${\n feedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-status-error-border/40 bg-status-error-bg/10 text-status-error-text\"\n }`}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{feedback.message}</span>\n <button\n onClick={() => setFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {filteredApps.length > 0 && (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-pop)]\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel=\"Select all visible applications\"\n />\n <span className=\"text-sm font-medium\">\n Select all visible\n </span>\n </div>\n {selectedAppIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedAppIds.size} selected\n </span>\n <button\n onClick={() => setBulkAction(\"deactivate\")}\n disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n aria-disabled={\n deactivatableApps.length === 0 || !updatePerm.allowed\n }\n title={\n !updatePerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md border border-status-warning-bg px-3 py-2 text-sm font-medium text-status-warning-text transition-colors hover:bg-status-warning-bg-subtle disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Archive size={16} aria-hidden=\"true\" />\n {tr(\"devportal.deactivateSelected\", \"Deactivate Selected\")}\n {deactivatableApps.length > 0\n ? ` (${deactivatableApps.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredApps.length} application\n {filteredApps.length !== 1 ? \"s\" : \"\"} found\n </p>\n\n {/* Applications */}\n {filteredApps.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredApps.map((app: Application) => (\n <AppCard\n key={app.id}\n app={app}\n selected={selectedAppIds.has(app.id)}\n onToggleSelect={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n />\n ))}\n </div>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken text-left\">\n <th className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisible}\n ariaLabel=\"Select all\"\n />\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Name\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Type\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\">\n Created\n </th>\n <th className=\"px-4 py-3 font-medium text-text-secondary\"></th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {filteredApps.map((app: Application) => (\n <tr\n key={app.id}\n className={`hover:bg-bg-surface-hover transition-colors ${selectedAppIds.has(app.id) ? \"bg-action-primary-bg/5\" : \"\"}`}\n >\n <td className=\"w-10 px-4 py-3\">\n <SelectionCheckbox\n checked={selectedAppIds.has(app.id)}\n onChange={(checked) =>\n toggleAppSelection(app.id, checked)\n }\n ariaLabel={`Select ${app.name}`}\n />\n </td>\n <td className=\"px-4 py-3\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"group flex items-center gap-3 text-left\"\n >\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt=\"\"\n className=\"h-8 w-8 rounded object-cover\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded bg-bg-sunken text-text-secondary\">\n <AppWindow size={14} />\n </div>\n )}\n <div>\n <p className=\"font-medium text-text-primary group-hover:text-action-primary-text transition-colors\">\n {app.name}\n </p>\n {app.shortDescription && (\n <p className=\"text-xs text-text-secondary line-clamp-1\">\n {app.shortDescription}\n </p>\n )}\n </div>\n </button>\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge status={app.status} />\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {APP_TYPE_LABELS[app.appType] || app.appType}\n </td>\n <td className=\"px-4 py-3 text-text-secondary tabular-nums\">\n {new Date(app.createdAt).toLocaleDateString()}\n </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n className=\"rounded-md px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors\"\n >\n View\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n ) : (\n <EmptyState\n illustration={\n searchQuery || statusFilter || typeFilter\n ? \"empty-search\"\n : \"empty-data\"\n }\n icon={<AppWindow size={32} />}\n title={tr(\"devportal.noApplicationsFound\", \"No applications found\")}\n description={\n searchQuery || statusFilter || typeFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstApp\",\n \"Create your first application to get started\",\n )\n }\n action={\n !searchQuery && !statusFilter && !typeFilter ? (\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n <ConfirmDialog\n open={bulkAction === \"deactivate\"}\n onOpenChange={(open) => {\n if (!open) setBulkAction(null);\n }}\n onConfirm={runBulkDeactivate}\n title=\"Deactivate selected applications\"\n description={`Deactivate ${deactivatableApps.length} selected application${deactivatableApps.length === 1 ? \"\" : \"s\"}? Users and integrations may lose access until these apps are re-enabled.`}\n confirmLabel=\"Deactivate\"\n cancelLabel=\"Cancel\"\n variant=\"warning\"\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -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;AA0ThC,eAAO,MAAM,qBAAqB,EAAE,
|
|
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;AA0ThC,eAAO,MAAM,qBAAqB,EAAE,EAmmBnC,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 GitFork,\n Info,\n Loader2,\n Puzzle,\n Save,\n X,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalEventBus } from \"../events\";\nimport { cn } from \"../utils/cn\";\nimport {\n useCreateApplicationMutation,\n useGetMyOrganizationsQuery,\n} from \"../../generated/global-operations\";\n\n// ─── Builder types ────────────────────────────────────────────────────────────\n\ntype BuilderType = \"app\" | \"node\" | \"workflow\";\n\n// ─── Permissions (App type) ───────────────────────────────────────────────────\n\nconst RBAC_RESOURCES = [\n {\n id: \"auth\",\n label: \"Authentication\",\n description: \"User identity and session management\",\n },\n {\n id: \"billing\",\n label: \"Billing\",\n description: \"Billing accounts and subscriptions\",\n },\n {\n id: \"files\",\n label: \"Files\",\n description: \"File storage and management\",\n },\n {\n id: \"tags\",\n label: \"Tags\",\n description: \"Tags and labels on resources\",\n },\n {\n id: \"calendar\",\n label: \"Calendar\",\n description: \"Events and scheduling\",\n },\n {\n id: \"workflows\",\n label: \"Workflows\",\n description: \"Automation workflows\",\n },\n {\n id: \"channels\",\n label: \"Channels\",\n description: \"Messaging channels\",\n },\n {\n id: \"notifications\",\n label: \"Notifications\",\n description: \"Platform notifications\",\n },\n {\n id: \"users\",\n label: \"Users & Members\",\n description: \"Workspace members and profiles\",\n },\n {\n id: \"workspaces\",\n label: \"Workspaces\",\n description: \"Workspace settings and metadata\",\n },\n {\n id: \"content\",\n label: \"Content\",\n description: \"Pages, documents, and content\",\n },\n {\n id: \"analytics\",\n label: \"Analytics\",\n description: \"Usage analytics and reporting\",\n },\n {\n id: \"webhooks\",\n label: \"Webhooks\",\n description: \"Webhook subscriptions\",\n },\n {\n id: \"rbac\",\n label: \"Roles & Permissions\",\n description: \"Access control configuration\",\n },\n] as const;\n\ntype ResourceId = (typeof RBAC_RESOURCES)[number][\"id\"];\ntype PermissionLevel = \"none\" | \"read\" | \"write\";\n\ntype PermissionMap = Record<ResourceId, PermissionLevel>;\n\nfunction defaultPermissions(): PermissionMap {\n return Object.fromEntries(\n RBAC_RESOURCES.map((r) => [r.id, \"none\"]),\n ) as PermissionMap;\n}\n\nfunction permissionsToManifest(perms: PermissionMap) {\n const entries = Object.entries(perms).filter(([, level]) => level !== \"none\");\n return entries.map(([resource, level]) => ({ resource, level }));\n}\n\n// ─── Org options ──────────────────────────────────────────────────────────────\n\nconst 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 if (builderType === \"node\") return \"NODE\" as const;\n if (builderType === \"workflow\") return \"WORKFLOW\" as const;\n return \"API_INTEGRATION\" as const;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate() || !builderType) return;\n\n const permManifest =\n builderType === \"app\"\n ? { permissions: permissionsToManifest(permissions) }\n : null;\n\n try {\n const result = await createApplication({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n shortDescription: formData.shortDescription || undefined,\n description: formData.description || undefined,\n category: formData.category || undefined,\n appType: appTypeForBuilder(),\n visibility: formData.visibility as\n | \"PUBLIC\"\n | \"PRIVATE\"\n | \"UNLISTED\"\n | undefined,\n developerOrganizationId:\n formData.developerOrganizationId || undefined,\n websiteUrl: formData.websiteUrl || undefined,\n githubUrl: formData.githubUrl || undefined,\n documentationUrl: formData.documentationUrl || undefined,\n supportUrl: formData.supportUrl || undefined,\n manifest: permManifest ?? undefined,\n 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 permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title=\"What do you want to build?\"\n description=\"Choose a builder type to get started\"\n purpose=\"Pick the kind of integration you are creating — a standard app, a Node package, or a workflow. Your choice sets up the right defaults (credentials, permissions, and where it can be installed) so you only fill in what that type actually needs.\"\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <div className=\"max-w-3xl space-y-6\">\n <div className=\"grid gap-4 sm:grid-cols-3\">\n <TypeCard\n icon={Box}\n title=\"App\"\n description=\"Build integrations that connect your service to the platform using OAuth tokens.\"\n bullets={[\n \"GitHub-style permissions\",\n \"OAuth token flow\",\n \"Webhook events\",\n \"Publish to app store\",\n ]}\n selected={builderType === \"app\"}\n onClick={() => setBuilderType(\"app\")}\n />\n <TypeCard\n icon={Puzzle}\n title=\"Node\"\n description=\"Build custom FluidGrids nodes that users can drag into their visual workflows.\"\n bullets={[\n \"ZIP bundle + manifest\",\n \"Custom inputs/outputs\",\n \"Versioned releases\",\n \"Publish to node store\",\n ]}\n selected={builderType === \"node\"}\n onClick={() => setBuilderType(\"node\")}\n />\n <TypeCard\n icon={GitFork}\n title=\"Workflow\"\n description=\"Build reusable automation workflows that users can import and customize.\"\n bullets={[\n \"Manifest + workflow JSON\",\n \"Trigger definitions\",\n \"Versioned releases\",\n \"Publish to workflow store\",\n ]}\n selected={builderType === \"workflow\"}\n onClick={() => setBuilderType(\"workflow\")}\n />\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 flex gap-3 text-sm text-text-secondary\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <p>\n Apps use OAuth to interact with platform data. Nodes and\n Workflows are packaged files you upload after creation.\n </p>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // ── Step 2: form ────────────────────────────────────────────────────────────\n const typeLabel =\n builderType === \"app\"\n ? \"App\"\n : builderType === \"node\"\n ? \"Node\"\n : \"Workflow\";\n const typeIcon =\n builderType === \"app\" ? (\n <Box size={16} />\n ) : builderType === \"node\" ? (\n <Puzzle size={16} />\n ) : (\n <GitFork size={16} />\n );\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title={`Create ${typeLabel}`}\n description={\n builderType === \"app\"\n ? \"Register a new app integration with OAuth permissions\"\n : builderType === \"node\"\n ? \"Register a new FluidGrids node package\"\n : \"Register a new reusable automation workflow\"\n }\n purpose={\n builderType === \"workflow\"\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 : builderType === \"node\"\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 : \"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 }\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Type badge + back to picker */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setBuilderType(null)}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft size={14} />\n Change type\n </button>\n <span className=\"flex items-center gap-1.5 rounded-full border border-border-subtle bg-bg-sunken px-3 py-1 text-xs font-medium\">\n {typeIcon}\n {typeLabel}\n </span>\n </div>\n\n {/* Basic info */}\n <PageSection\n title=\"Basic Information\"\n description=\"Core details about your application\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Name\"\n value={formData.name}\n onChange={handleNameChange}\n placeholder={\n builderType === \"app\"\n ? \"My Integration\"\n : builderType === \"node\"\n ? \"HTTP Request\"\n : \"Onboarding Flow\"\n }\n required\n error={errors.name}\n />\n <InputField\n label=\"Slug\"\n value={formData.slug}\n onChange={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 <TextareaField\n label=\"Short Description\"\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder=\"Briefly describe what this does\"\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 characters`}\n rows={2}\n />\n <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' 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 <TextareaField\n label=\"Full Description\"\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder=\"Detailed description, features, and use cases...\"\n rows={5}\n hint=\"Markdown supported\"\n />\n </div>\n </PageSection>\n\n {/* 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 & 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 Workflow, go to the Versions tab to upload your manifest and workflow JSON definition.\"}\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":"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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 GitFork,\n Info,\n Loader2,\n Puzzle,\n Save,\n X,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalEventBus } from \"../events\";\nimport { cn } from \"../utils/cn\";\nimport {\n useCreateApplicationMutation,\n useGetMyOrganizationsQuery,\n} from \"../../generated/global-operations\";\n\n// ─── Builder types ────────────────────────────────────────────────────────────\n\ntype BuilderType = \"app\" | \"node\" | \"workflow\";\n\n// ─── Permissions (App type) ───────────────────────────────────────────────────\n\nconst RBAC_RESOURCES = [\n {\n id: \"auth\",\n label: \"Authentication\",\n description: \"User identity and session management\",\n },\n {\n id: \"billing\",\n label: \"Billing\",\n description: \"Billing accounts and subscriptions\",\n },\n {\n id: \"files\",\n label: \"Files\",\n description: \"File storage and management\",\n },\n {\n id: \"tags\",\n label: \"Tags\",\n description: \"Tags and labels on resources\",\n },\n {\n id: \"calendar\",\n label: \"Calendar\",\n description: \"Events and scheduling\",\n },\n {\n id: \"workflows\",\n label: \"Workflows\",\n description: \"Automation workflows\",\n },\n {\n id: \"channels\",\n label: \"Channels\",\n description: \"Messaging channels\",\n },\n {\n id: \"notifications\",\n label: \"Notifications\",\n description: \"Platform notifications\",\n },\n {\n id: \"users\",\n label: \"Users & Members\",\n description: \"Workspace members and profiles\",\n },\n {\n id: \"workspaces\",\n label: \"Workspaces\",\n description: \"Workspace settings and metadata\",\n },\n {\n id: \"content\",\n label: \"Content\",\n description: \"Pages, documents, and content\",\n },\n {\n id: \"analytics\",\n label: \"Analytics\",\n description: \"Usage analytics and reporting\",\n },\n {\n id: \"webhooks\",\n label: \"Webhooks\",\n description: \"Webhook subscriptions\",\n },\n {\n id: \"rbac\",\n label: \"Roles & Permissions\",\n description: \"Access control configuration\",\n },\n] as const;\n\ntype ResourceId = (typeof RBAC_RESOURCES)[number][\"id\"];\ntype PermissionLevel = \"none\" | \"read\" | \"write\";\n\ntype PermissionMap = Record<ResourceId, PermissionLevel>;\n\nfunction defaultPermissions(): PermissionMap {\n return Object.fromEntries(\n RBAC_RESOURCES.map((r) => [r.id, \"none\"]),\n ) as PermissionMap;\n}\n\nfunction permissionsToManifest(perms: PermissionMap) {\n const entries = Object.entries(perms).filter(([, level]) => level !== \"none\");\n return entries.map(([resource, level]) => ({ resource, level }));\n}\n\n// ─── Org options ──────────────────────────────────────────────────────────────\n\nconst 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 if (builderType === \"node\") return \"NODE\" as const;\n if (builderType === \"workflow\") return \"WORKFLOW\" as const;\n return \"API_INTEGRATION\" as const;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate() || !builderType) return;\n\n const permManifest =\n builderType === \"app\"\n ? { permissions: permissionsToManifest(permissions) }\n : null;\n\n try {\n const result = await createApplication({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n shortDescription: formData.shortDescription || undefined,\n description: formData.description || undefined,\n category: formData.category || undefined,\n appType: appTypeForBuilder(),\n visibility: formData.visibility as\n \"PUBLIC\" | \"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 permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title=\"What do you want to build?\"\n description=\"Choose a builder type to get started\"\n purpose=\"Pick the kind of integration you are creating — a standard app, a Node package, or a workflow. Your choice sets up the right defaults (credentials, permissions, and where it can be installed) so you only fill in what that type actually needs.\"\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <div className=\"max-w-3xl space-y-6\">\n <div className=\"grid gap-4 sm:grid-cols-3\">\n <TypeCard\n icon={Box}\n title=\"App\"\n description=\"Build integrations that connect your service to the platform using OAuth tokens.\"\n bullets={[\n \"GitHub-style permissions\",\n \"OAuth token flow\",\n \"Webhook events\",\n \"Publish to app store\",\n ]}\n selected={builderType === \"app\"}\n onClick={() => setBuilderType(\"app\")}\n />\n <TypeCard\n icon={Puzzle}\n title=\"Node\"\n description=\"Build custom FluidGrids nodes that users can drag into their visual workflows.\"\n bullets={[\n \"ZIP bundle + manifest\",\n \"Custom inputs/outputs\",\n \"Versioned releases\",\n \"Publish to node store\",\n ]}\n selected={builderType === \"node\"}\n onClick={() => setBuilderType(\"node\")}\n />\n <TypeCard\n icon={GitFork}\n title=\"Workflow\"\n description=\"Build reusable automation workflows that users can import and customize.\"\n bullets={[\n \"Manifest + workflow JSON\",\n \"Trigger definitions\",\n \"Versioned releases\",\n \"Publish to workflow store\",\n ]}\n selected={builderType === \"workflow\"}\n onClick={() => setBuilderType(\"workflow\")}\n />\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 flex gap-3 text-sm text-text-secondary\">\n <Info size={16} className=\"flex-shrink-0 mt-0.5\" />\n <p>\n Apps use OAuth to interact with platform data. Nodes and\n Workflows are packaged files you upload after creation.\n </p>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // ── Step 2: form ────────────────────────────────────────────────────────────\n const typeLabel =\n builderType === \"app\"\n ? \"App\"\n : builderType === \"node\"\n ? \"Node\"\n : \"Workflow\";\n const typeIcon =\n builderType === \"app\" ? (\n <Box size={16} />\n ) : builderType === \"node\" ? (\n <Puzzle size={16} />\n ) : (\n <GitFork size={16} />\n );\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title={`Create ${typeLabel}`}\n description={\n builderType === \"app\"\n ? \"Register a new app integration with OAuth permissions\"\n : builderType === \"node\"\n ? \"Register a new FluidGrids node package\"\n : \"Register a new reusable automation workflow\"\n }\n purpose={\n builderType === \"workflow\"\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 : builderType === \"node\"\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 : \"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 }\n backLink={`${basePath}/apps`}\n backLabel=\"Back to Applications\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Type badge + back to picker */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => setBuilderType(null)}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft size={14} />\n Change type\n </button>\n <span className=\"flex items-center gap-1.5 rounded-full border border-border-subtle bg-bg-sunken px-3 py-1 text-xs font-medium\">\n {typeIcon}\n {typeLabel}\n </span>\n </div>\n\n {/* Basic info */}\n <PageSection\n title=\"Basic Information\"\n description=\"Core details about your application\"\n >\n <div className=\"space-y-4\">\n <InputField\n label=\"Name\"\n value={formData.name}\n onChange={handleNameChange}\n placeholder={\n builderType === \"app\"\n ? \"My Integration\"\n : builderType === \"node\"\n ? \"HTTP Request\"\n : \"Onboarding Flow\"\n }\n required\n error={errors.name}\n />\n <InputField\n label=\"Slug\"\n value={formData.slug}\n onChange={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 <TextareaField\n label=\"Short Description\"\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder=\"Briefly describe what this does\"\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 characters`}\n rows={2}\n />\n <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' 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 <TextareaField\n label=\"Full Description\"\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder=\"Detailed description, features, and use cases...\"\n rows={5}\n hint=\"Markdown supported\"\n />\n </div>\n </PageSection>\n\n {/* 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 & 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 Workflow, go to the Versions tab to upload your manifest and workflow JSON definition.\"}\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":"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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;AAsKhC;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,
|
|
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;AAsKhC;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,EAwiCjC,CAAC"}
|