@burdenoff/microfe-devportal 2026.527.1 → 2026.530.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/components/EmptyState.js +9 -23
- package/dist/devportal/components/EmptyState.js.map +1 -1
- package/dist/devportal/components/PageLayout.js +5 -2
- package/dist/devportal/components/PageLayout.js.map +1 -1
- package/dist/devportal/components/TelemetryErrorBoundary.js +37 -17
- package/dist/devportal/components/TelemetryErrorBoundary.js.map +1 -1
- package/dist/devportal/hooks/useGraphQL.js +2 -9
- package/dist/devportal/hooks/useGraphQL.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +1 -1
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +78 -73
- package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +135 -131
- package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js.map +1 -1
- package/dist/devportal/pages/DashboardPage.js +36 -28
- package/dist/devportal/pages/DashboardPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +3 -3
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/SettingsPage.js +10 -10
- package/dist/devportal/pages/SettingsPage.js.map +1 -1
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditApplicationPage.js","names":[],"sources":["../../../src/devportal/pages/EditApplicationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useParams } from \"react-router-dom\";\nimport {\n Save,\n Trash2,\n AlertTriangle,\n Archive,\n Send,\n AlertCircle,\n AppWindow,\n Power,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n TagInput,\n CheckboxField,\n} from \"../components/FormField\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { LoadingState } from \"../components/LoadingState\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { Application, AppType, AppVisibility, AppStatus } from \"../types\";\nimport { APP_TYPE_LABELS } from \"../utils/constants\";\nimport {\n useGetApplicationQuery,\n useUpdateApplicationMutation,\n useDeleteApplicationMutation,\n useSubmitApplicationMutation,\n useDeactivateAppMutation,\n} from \"../../generated/global-operations\";\n\ninterface EditApplicationFormData {\n name: string;\n slug: string;\n shortDescription: string;\n description: string;\n appType: AppType;\n visibility: AppVisibility;\n category: string;\n tags: string[];\n keywords: string[];\n websiteUrl: string;\n documentationUrl: string;\n supportUrl: string;\n privacyPolicyUrl: string;\n termsOfServiceUrl: string;\n githubUrl: string;\n requiresApproval: boolean;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n shortDescription?: string;\n appType?: string;\n category?: string;\n}\n\nconst APP_TYPE_OPTIONS = Object.entries(APP_TYPE_LABELS).map(\n ([value, label]) => ({\n value,\n label,\n }),\n);\n\nconst VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public - Visible to everyone\" },\n { value: \"PRIVATE\", label: \"Private - Only visible to you\" },\n { value: \"UNLISTED\", label: \"Unlisted - Accessible via direct link\" },\n { value: \"INTERNAL\", label: \"Internal - Only visible within organization\" },\n];\n\nconst CATEGORY_OPTIONS = [\n { value: \"productivity\", label: \"Productivity\" },\n { value: \"communication\", label: \"Communication\" },\n { value: \"analytics\", label: \"Analytics\" },\n { value: \"automation\", label: \"Automation\" },\n { value: \"developer-tools\", label: \"Developer Tools\" },\n { value: \"finance\", label: \"Finance\" },\n { value: \"hr\", label: \"HR & People\" },\n { value: \"marketing\", label: \"Marketing\" },\n { value: \"project-management\", label: \"Project Management\" },\n { value: \"security\", label: \"Security\" },\n { value: \"support\", label: \"Support\" },\n { value: \"other\", label: \"Other\" },\n];\n\n/**\n * Helper to safely map GraphQL response to Application type\n */\nfunction mapToApplication(\n data: NonNullable<\n ReturnType<typeof useGetApplicationQuery>[\"data\"]\n >[\"application\"],\n): Application | null {\n if (!data) return null;\n return {\n id: data.id,\n name: data.name,\n slug: data.slug,\n shortDescription: data.shortDescription || undefined,\n description: data.description || undefined,\n appType: data.appType,\n status: data.status,\n visibility: data.visibility,\n tenantId: data.tenantId,\n developerId: data.developerId,\n developerOrganizationId: data.developerOrganizationId || undefined,\n category: data.category || undefined,\n tags: data.tags || [],\n keywords: data.keywords || [],\n iconUrl: data.iconUrl || undefined,\n websiteUrl: data.websiteUrl || undefined,\n documentationUrl: data.documentationUrl || undefined,\n supportUrl: data.supportUrl || undefined,\n privacyPolicyUrl: data.privacyPolicyUrl || undefined,\n termsOfServiceUrl: data.termsOfServiceUrl || undefined,\n githubUrl: data.githubUrl || undefined,\n pricingType: data.pricingType || undefined,\n installCount: data.installCount,\n activeInstallCount: data.activeInstallCount,\n averageRating: data.averageRating ?? undefined,\n reviewCount: data.reviewCount,\n submittedAt: data.submittedAt || undefined,\n publishedAt: data.publishedAt || undefined,\n lastReviewedAt: data.lastReviewedAt || undefined,\n reviewStatus: data.reviewStatus,\n reviewNotes: data.reviewNotes || undefined,\n reviewedBy: data.reviewedBy || undefined,\n isFeatured: data.isFeatured,\n isVerified: data.isVerified,\n requiresApproval: data.requiresApproval,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n };\n}\n\n/**\n * Edit application page — wired to real GraphQL mutations.\n */\nexport const EditApplicationPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { appId } = useParams<{ appId: string }>();\n const { t } = useI18n();\n const { allowed: canUpdate } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_UPDATE,\n );\n const { allowed: canDelete } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_DELETE,\n );\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<FormErrors>({});\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showDeprecateDialog, setShowDeprecateDialog] = useState(false);\n const [showDeactivateDialog, setShowDeactivateDialog] = useState(false);\n const [hasChanges, setHasChanges] = useState(false);\n\n // ---------------------------------------------------------------------------\n // GraphQL queries & mutations\n // ---------------------------------------------------------------------------\n const {\n data,\n loading,\n error: queryError,\n } = useGetApplicationQuery({\n variables: { id: appId! },\n skip: !appId,\n });\n\n const [updateApplication] = useUpdateApplicationMutation();\n const [deleteApplication] = useDeleteApplicationMutation();\n const [submitApplication] = useSubmitApplicationMutation();\n const [deactivateApp] = useDeactivateAppMutation();\n\n // ---------------------------------------------------------------------------\n // Derive application from query data\n // ---------------------------------------------------------------------------\n const app = mapToApplication(data?.application);\n\n // ---------------------------------------------------------------------------\n // Form state — initialised from fetched app once available\n // ---------------------------------------------------------------------------\n const [formData, setFormData] = useState<EditApplicationFormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n appType: \"WEB_APP\",\n visibility: \"PUBLIC\",\n category: \"\",\n tags: [],\n keywords: [],\n websiteUrl: \"\",\n documentationUrl: \"\",\n supportUrl: \"\",\n privacyPolicyUrl: \"\",\n termsOfServiceUrl: \"\",\n githubUrl: \"\",\n requiresApproval: false,\n });\n\n useEffect(() => {\n if (app) {\n setFormData({\n name: app.name,\n slug: app.slug,\n shortDescription: app.shortDescription || \"\",\n description: app.description || \"\",\n appType: app.appType,\n visibility: app.visibility,\n category: app.category || \"\",\n tags: app.tags,\n keywords: app.keywords,\n websiteUrl: app.websiteUrl || \"\",\n documentationUrl: app.documentationUrl || \"\",\n supportUrl: app.supportUrl || \"\",\n privacyPolicyUrl: app.privacyPolicyUrl || \"\",\n termsOfServiceUrl: app.termsOfServiceUrl || \"\",\n githubUrl: app.githubUrl || \"\",\n requiresApproval: app.requiresApproval,\n });\n setHasChanges(false);\n }\n // Re-sync form when the underlying application data changes (e.g. after\n // a refetch triggered by a mutation). We intentionally depend on\n // `data?.application` rather than the derived `app` object so that the\n // effect fires when the query cache updates.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [data?.application]);\n\n // ---------------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------------\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleChange = (\n field: keyof EditApplicationFormData,\n value: string | string[] | boolean,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n setHasChanges(true);\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const validateForm = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.appNameRequired\",\n \"Application name is required\",\n );\n } else if (formData.name.length < 3) {\n newErrors.name = tr(\n \"devportal.appNameMinLength\",\n \"Name must be at least 3 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.appNameMaxLength\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalidChars\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (!formData.shortDescription.trim()) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionRequired\",\n \"Short description is required\",\n );\n } else if (formData.shortDescription.length > 200) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionMaxLength\",\n \"Short description must be less than 200 characters\",\n );\n }\n\n if (!formData.appType) {\n newErrors.appType = tr(\n \"devportal.appTypeRequired\",\n \"Application type is required\",\n );\n }\n\n if (!formData.category) {\n newErrors.category = tr(\n \"devportal.categoryRequired\",\n \"Category is required\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm() || !app) {\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n name: formData.name,\n shortDescription: formData.shortDescription,\n description: formData.description,\n appType: formData.appType,\n visibility: formData.visibility,\n category: formData.category,\n tags: formData.tags,\n keywords: formData.keywords,\n websiteUrl: formData.websiteUrl || null,\n documentationUrl: formData.documentationUrl || null,\n supportUrl: formData.supportUrl || null,\n privacyPolicyUrl: formData.privacyPolicyUrl || null,\n termsOfServiceUrl: formData.termsOfServiceUrl || null,\n githubUrl: formData.githubUrl || null,\n },\n },\n });\n setHasChanges(false);\n } catch (err) {\n console.error(\"Failed to update application:\", err);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleDelete = async () => {\n if (!app) return;\n void nativeImpact(\"medium\");\n try {\n await deleteApplication({\n variables: { id: app.id },\n });\n void nativeNotify(\"success\");\n handleNavigate(\"/apps\");\n } catch (err) {\n void nativeNotify(\"error\");\n console.error(\"Failed to delete application:\", err);\n }\n };\n\n const handleDeprecate = async () => {\n if (!app) return;\n try {\n // Deprecate is modelled as a status transition via update. The backend\n // treats a visibility change to PRIVATE + status mutation or a dedicated\n // deprecation endpoint. Here we use submitApplication with a status note\n // as the deprecate action since there is no separate `deprecateApplication`\n // mutation. If a dedicated mutation is added later, swap it in here.\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n visibility: \"PRIVATE\",\n },\n },\n });\n setShowDeprecateDialog(false);\n } catch (err) {\n console.error(\"Failed to deprecate application:\", err);\n }\n };\n\n const handleDeactivate = async () => {\n if (!app) return;\n try {\n await deactivateApp({\n variables: { id: app.id },\n });\n setShowDeactivateDialog(false);\n handleNavigate(\"/apps\");\n } catch (err) {\n console.error(\"Failed to deactivate application:\", err);\n }\n };\n\n const handleSubmitForReview = async () => {\n if (!app) return;\n try {\n setIsSubmitting(true);\n await submitApplication({\n variables: { id: app.id },\n });\n } catch (err) {\n console.error(\"Failed to submit for review:\", err);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Loading / Error / Not-found states\n // ---------------------------------------------------------------------------\n if (loading) {\n return (\n <LoadingState\n message={tr(\"devportal.loadingApplication\", \"Loading application...\")}\n />\n );\n }\n\n if (queryError) {\n return (\n <PageLayout\n title={tr(\"devportal.error\", \"Error\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle size={48} className=\"text-destructive mb-4\" />\n <h3 className=\"text-lg font-semibold mb-2\">\n {tr(\n \"devportal.failedToLoadApplication\",\n \"Failed to load application\",\n )}\n </h3>\n <p className=\"text-sm text-muted-foreground\">{queryError.message}</p>\n </div>\n </PageLayout>\n );\n }\n\n if (!app) {\n return (\n <PageLayout\n title={tr(\"devportal.notFound\", \"Not Found\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <EmptyState\n icon={<AppWindow size={48} />}\n title={tr(\"devportal.applicationNotFound\", \"Application not found\")}\n description={tr(\n \"devportal.applicationNotFoundDescription\",\n \"The application you're looking for doesn't exist or you don't have access to it.\",\n )}\n />\n </PageLayout>\n );\n }\n\n // ---------------------------------------------------------------------------\n // Derived flags\n // ---------------------------------------------------------------------------\n const canSubmitForReview =\n app.status === \"DRAFT\" || app.status === \"REJECTED\";\n const canDeprecate = app.status === \"PUBLISHED\";\n const canDeactivate = app.status === \"ACTIVE\" || app.status === \"PUBLISHED\";\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_UPDATE}>\n <PageLayout\n title={`${tr(\"devportal.editAppTitlePrefix\", \"Edit:\")} ${app.name}`}\n description={tr(\n \"devportal.editAppDescription\",\n \"Update your application settings\",\n )}\n backLink={`${basePath}/apps/${app.id}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={app.status} type=\"app\" />\n </div>\n }\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Status banner for published apps */}\n {app.status === \"PUBLISHED\" && (\n <div className=\"rounded-lg border border-border-subtle bg-status-warning-bg-subtle p-4 flex gap-3\">\n <AlertTriangle\n size={20}\n className=\"text-status-warning-text flex-shrink-0 mt-0.5\"\n />\n <div className=\"text-sm text-status-warning-text\">\n <p className=\"font-medium\">\n {tr(\n \"devportal.appIsPublished\",\n \"This application is published\",\n )}\n </p>\n <p className=\"mt-1 text-status-warning-text\">\n {tr(\n \"devportal.publishedAppChangesWarning\",\n \"Changes to published apps may require re-review. Major changes will put the app back into review status.\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {/* Basic Information */}\n <PageSection\n title={tr(\"devportal.basicInformation\", \"Basic Information\")}\n description={tr(\n \"devportal.basicInformationDescription\",\n \"Core details about your application\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.applicationName\", \"Application Name\")}\n value={formData.name}\n onChange={(e) => handleChange(\"name\", e.target.value)}\n placeholder={tr(\n \"devportal.applicationNamePlaceholder\",\n \"My Awesome App\",\n )}\n required\n error={errors.name}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.slugPlaceholder\", \"my-awesome-app\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugChangeWarning\",\n \"Changing the slug may break existing integrations\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.shortDescription\", \"Short Description\")}\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder={tr(\n \"devportal.shortDescriptionPlaceholder\",\n \"A brief description of what your app does\",\n )}\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 ${tr(\"devportal.characters\", \"characters\")}`}\n rows={2}\n />\n\n <TextareaField\n label={tr(\"devportal.fullDescription\", \"Full Description\")}\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder={tr(\n \"devportal.fullDescriptionPlaceholder\",\n \"Detailed description of your application, features, and use cases...\",\n )}\n rows={6}\n hint={tr(\n \"devportal.markdownSupported\",\n \"Markdown formatting is supported\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Classification */}\n <PageSection\n title={tr(\"devportal.classification\", \"Classification\")}\n description={tr(\n \"devportal.classificationDescription\",\n \"Categorize your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label={tr(\"devportal.applicationType\", \"Application Type\")}\n value={formData.appType}\n onChange={(e) => handleChange(\"appType\", e.target.value)}\n options={APP_TYPE_OPTIONS}\n required\n error={errors.appType}\n />\n\n <SelectField\n label={tr(\"devportal.category\", \"Category\")}\n value={formData.category}\n onChange={(e) => handleChange(\"category\", e.target.value)}\n options={CATEGORY_OPTIONS}\n required\n error={errors.category}\n />\n\n <SelectField\n label={tr(\"devportal.visibility\", \"Visibility\")}\n value={formData.visibility}\n onChange={(e) =>\n handleChange(\"visibility\", e.target.value as AppVisibility)\n }\n options={VISIBILITY_OPTIONS}\n />\n </div>\n\n <TagInput\n label={tr(\"devportal.tags\", \"Tags\")}\n value={formData.tags}\n onChange={(tags) => handleChange(\"tags\", tags)}\n placeholder={tr(\n \"devportal.addTagsPlaceholder\",\n \"Add tags (press Enter)\",\n )}\n />\n\n <TagInput\n label={tr(\"devportal.keywords\", \"Keywords\")}\n value={formData.keywords}\n onChange={(keywords) => handleChange(\"keywords\", keywords)}\n placeholder={tr(\n \"devportal.addKeywordsPlaceholder\",\n \"Add keywords (press Enter)\",\n )}\n />\n </PageSection>\n\n {/* URLs */}\n <PageSection\n title={tr(\"devportal.links\", \"Links\")}\n description={tr(\n \"devportal.linksDescription\",\n \"External resources for your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.websiteUrlPlaceholder\",\n \"https://example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.documentationUrl\", \"Documentation URL\")}\n type=\"url\"\n value={formData.documentationUrl}\n onChange={(e) =>\n handleChange(\"documentationUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.documentationUrlPlaceholder\",\n \"https://docs.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.supportUrl\", \"Support URL\")}\n type=\"url\"\n value={formData.supportUrl}\n onChange={(e) => handleChange(\"supportUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.supportUrlPlaceholder\",\n \"https://support.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.githubUrl\", \"GitHub URL\")}\n type=\"url\"\n value={formData.githubUrl}\n onChange={(e) => handleChange(\"githubUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.githubUrlPlaceholder\",\n \"https://github.com/org/repo\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.privacyPolicyUrl\", \"Privacy Policy URL\")}\n type=\"url\"\n value={formData.privacyPolicyUrl}\n onChange={(e) =>\n handleChange(\"privacyPolicyUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.privacyPolicyUrlPlaceholder\",\n \"https://example.com/privacy\",\n )}\n />\n\n <InputField\n label={tr(\n \"devportal.termsOfServiceUrl\",\n \"Terms of Service URL\",\n )}\n type=\"url\"\n value={formData.termsOfServiceUrl}\n onChange={(e) =>\n handleChange(\"termsOfServiceUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.termsOfServiceUrlPlaceholder\",\n \"https://example.com/terms\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Settings */}\n <PageSection\n title={tr(\"devportal.settings\", \"Settings\")}\n description={tr(\n \"devportal.settingsDescription\",\n \"Additional configuration options\",\n )}\n >\n <div className=\"space-y-4\">\n <CheckboxField\n label={tr(\n \"devportal.requireWorkspaceAdminApproval\",\n \"Require workspace admin approval\",\n )}\n description={tr(\n \"devportal.requireWorkspaceAdminApprovalDescription\",\n \"When enabled, workspace admins must approve installations before users can access this app\",\n )}\n checked={formData.requiresApproval}\n onChange={(e) =>\n handleChange(\"requiresApproval\", e.target.checked)\n }\n />\n </div>\n </PageSection>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-4 border-t pt-6\">\n {/* Primary actions */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n {canSubmitForReview && (\n <button\n type=\"button\"\n onClick={handleSubmitForReview}\n disabled={isSubmitting || hasChanges}\n className=\"inline-flex items-center gap-2 rounded-md border border-primary bg-primary/10 px-4 py-2 text-sm font-medium text-primary hover:bg-primary/20 transition-colors disabled:opacity-50\"\n >\n <Send size={16} />\n {tr(\"devportal.submitForReview\", \"Submit for Review\")}\n </button>\n )}\n </div>\n <button\n type=\"submit\"\n disabled={isSubmitting || !hasChanges || !canUpdate}\n aria-disabled={!canUpdate}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isSubmitting ? (\n <>\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.saving\", \"Saving...\")}\n </>\n ) : (\n <>\n <Save size={16} />\n {tr(\"devportal.saveChanges\", \"Save Changes\")}\n </>\n )}\n </button>\n </div>\n\n {/* Danger zone */}\n <div className=\"rounded-lg border border-border-subtle bg-status-error-bg-subtle p-4\">\n <h3 className=\"text-sm font-medium text-status-error-text\">\n {tr(\"devportal.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-error-text\">\n {tr(\n \"devportal.dangerZoneWarning\",\n \"These actions are irreversible. Please proceed with caution.\",\n )}\n </p>\n <div className=\"mt-4 flex gap-3\">\n {canDeprecate && (\n <button\n type=\"button\"\n onClick={() => setShowDeprecateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-white px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Archive size={14} />\n {tr(\"devportal.deprecateApp\", \"Deprecate App\")}\n </button>\n )}\n {canDeactivate && (\n <button\n type=\"button\"\n onClick={() => setShowDeactivateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-white px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Power size={14} />\n {tr(\"devportal.deactivateApp\", \"Deactivate App\")}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setShowDeleteDialog(true)}\n disabled={!canDelete}\n aria-disabled={!canDelete}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg bg-white px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n <Trash2 size={14} />\n {tr(\"devportal.deleteApp\", \"Delete App\")}\n </button>\n </div>\n </div>\n </div>\n </form>\n\n {/* Delete confirmation dialog */}\n <ConfirmDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n title={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n description={tr(\n \"devportal.deleteApplicationConfirm\",\n `Are you sure you want to delete \"${app.name}\"? This action cannot be undone. All OAuth clients, API keys, webhooks, and analytics data will be permanently deleted.`,\n )}\n confirmLabel={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n variant=\"destructive\"\n onConfirm={handleDelete}\n />\n\n {/* Deprecate confirmation dialog */}\n <ConfirmDialog\n open={showDeprecateDialog}\n onOpenChange={setShowDeprecateDialog}\n title={tr(\"devportal.deprecateApplication\", \"Deprecate Application\")}\n description={tr(\n \"devportal.deprecateApplicationConfirm\",\n `Are you sure you want to deprecate \"${app.name}\"? Existing users will still be able to use the app, but it will no longer be visible in the marketplace and new installations will be disabled.`,\n )}\n confirmLabel={tr(\n \"devportal.deprecateApplication\",\n \"Deprecate Application\",\n )}\n variant=\"warning\"\n onConfirm={handleDeprecate}\n />\n\n {/* Deactivate confirmation dialog */}\n <ConfirmDialog\n open={showDeactivateDialog}\n onOpenChange={setShowDeactivateDialog}\n title={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n description={tr(\n \"devportal.deactivateApplicationConfirm\",\n `Are you sure you want to deactivate \"${app.name}\"? This will revoke all active tokens and disable the app from the auth-svc layer. The app will no longer be able to authenticate or act on behalf of users.`,\n )}\n confirmLabel={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n variant=\"destructive\"\n onConfirm={handleDeactivate}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditApplicationPage.js","names":[],"sources":["../../../src/devportal/pages/EditApplicationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useParams } from \"react-router-dom\";\nimport {\n Save,\n Trash2,\n AlertTriangle,\n Archive,\n Send,\n AlertCircle,\n AppWindow,\n Power,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n TagInput,\n CheckboxField,\n} from \"../components/FormField\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { LoadingState } from \"../components/LoadingState\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { Application, AppType, AppVisibility, AppStatus } from \"../types\";\nimport { APP_TYPE_LABELS } from \"../utils/constants\";\nimport {\n useGetApplicationQuery,\n useUpdateApplicationMutation,\n useDeleteApplicationMutation,\n useSubmitApplicationMutation,\n useDeactivateAppMutation,\n} from \"../../generated/global-operations\";\n\ninterface EditApplicationFormData {\n name: string;\n slug: string;\n shortDescription: string;\n description: string;\n appType: AppType;\n visibility: AppVisibility;\n category: string;\n tags: string[];\n keywords: string[];\n websiteUrl: string;\n documentationUrl: string;\n supportUrl: string;\n privacyPolicyUrl: string;\n termsOfServiceUrl: string;\n githubUrl: string;\n requiresApproval: boolean;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n shortDescription?: string;\n appType?: string;\n category?: string;\n}\n\nconst APP_TYPE_OPTIONS = Object.entries(APP_TYPE_LABELS).map(\n ([value, label]) => ({\n value,\n label,\n }),\n);\n\nconst VISIBILITY_OPTIONS = [\n { value: \"PUBLIC\", label: \"Public - Visible to everyone\" },\n { value: \"PRIVATE\", label: \"Private - Only visible to you\" },\n { value: \"UNLISTED\", label: \"Unlisted - Accessible via direct link\" },\n { value: \"INTERNAL\", label: \"Internal - Only visible within organization\" },\n];\n\nconst CATEGORY_OPTIONS = [\n { value: \"productivity\", label: \"Productivity\" },\n { value: \"communication\", label: \"Communication\" },\n { value: \"analytics\", label: \"Analytics\" },\n { value: \"automation\", label: \"Automation\" },\n { value: \"developer-tools\", label: \"Developer Tools\" },\n { value: \"finance\", label: \"Finance\" },\n { value: \"hr\", label: \"HR & People\" },\n { value: \"marketing\", label: \"Marketing\" },\n { value: \"project-management\", label: \"Project Management\" },\n { value: \"security\", label: \"Security\" },\n { value: \"support\", label: \"Support\" },\n { value: \"other\", label: \"Other\" },\n];\n\n/**\n * Helper to safely map GraphQL response to Application type\n */\nfunction mapToApplication(\n data: NonNullable<\n ReturnType<typeof useGetApplicationQuery>[\"data\"]\n >[\"application\"],\n): Application | null {\n if (!data) return null;\n return {\n id: data.id,\n name: data.name,\n slug: data.slug,\n shortDescription: data.shortDescription || undefined,\n description: data.description || undefined,\n appType: data.appType,\n status: data.status,\n visibility: data.visibility,\n tenantId: data.tenantId,\n developerId: data.developerId,\n developerOrganizationId: data.developerOrganizationId || undefined,\n category: data.category || undefined,\n tags: data.tags || [],\n keywords: data.keywords || [],\n iconUrl: data.iconUrl || undefined,\n websiteUrl: data.websiteUrl || undefined,\n documentationUrl: data.documentationUrl || undefined,\n supportUrl: data.supportUrl || undefined,\n privacyPolicyUrl: data.privacyPolicyUrl || undefined,\n termsOfServiceUrl: data.termsOfServiceUrl || undefined,\n githubUrl: data.githubUrl || undefined,\n pricingType: data.pricingType || undefined,\n installCount: data.installCount,\n activeInstallCount: data.activeInstallCount,\n averageRating: data.averageRating ?? undefined,\n reviewCount: data.reviewCount,\n submittedAt: data.submittedAt || undefined,\n publishedAt: data.publishedAt || undefined,\n lastReviewedAt: data.lastReviewedAt || undefined,\n reviewStatus: data.reviewStatus,\n reviewNotes: data.reviewNotes || undefined,\n reviewedBy: data.reviewedBy || undefined,\n isFeatured: data.isFeatured,\n isVerified: data.isVerified,\n requiresApproval: data.requiresApproval,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n };\n}\n\n/**\n * Edit application page — wired to real GraphQL mutations.\n */\nexport const EditApplicationPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { appId } = useParams<{ appId: string }>();\n const { t } = useI18n();\n const { allowed: canUpdate } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_UPDATE,\n );\n const { allowed: canDelete } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.APP_DELETE,\n );\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<FormErrors>({});\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showDeprecateDialog, setShowDeprecateDialog] = useState(false);\n const [showDeactivateDialog, setShowDeactivateDialog] = useState(false);\n const [hasChanges, setHasChanges] = useState(false);\n\n // ---------------------------------------------------------------------------\n // GraphQL queries & mutations\n // ---------------------------------------------------------------------------\n const {\n data,\n loading,\n error: queryError,\n } = useGetApplicationQuery({\n variables: { id: appId! },\n skip: !appId,\n });\n\n const [updateApplication] = useUpdateApplicationMutation();\n const [deleteApplication] = useDeleteApplicationMutation();\n const [submitApplication] = useSubmitApplicationMutation();\n const [deactivateApp] = useDeactivateAppMutation();\n\n // ---------------------------------------------------------------------------\n // Derive application from query data\n // ---------------------------------------------------------------------------\n const app = mapToApplication(data?.application);\n\n // ---------------------------------------------------------------------------\n // Form state — initialised from fetched app once available\n // ---------------------------------------------------------------------------\n const [formData, setFormData] = useState<EditApplicationFormData>({\n name: \"\",\n slug: \"\",\n shortDescription: \"\",\n description: \"\",\n appType: \"WEB_APP\",\n visibility: \"PUBLIC\",\n category: \"\",\n tags: [],\n keywords: [],\n websiteUrl: \"\",\n documentationUrl: \"\",\n supportUrl: \"\",\n privacyPolicyUrl: \"\",\n termsOfServiceUrl: \"\",\n githubUrl: \"\",\n requiresApproval: false,\n });\n\n useEffect(() => {\n if (app) {\n setFormData({\n name: app.name,\n slug: app.slug,\n shortDescription: app.shortDescription || \"\",\n description: app.description || \"\",\n appType: app.appType,\n visibility: app.visibility,\n category: app.category || \"\",\n tags: app.tags,\n keywords: app.keywords,\n websiteUrl: app.websiteUrl || \"\",\n documentationUrl: app.documentationUrl || \"\",\n supportUrl: app.supportUrl || \"\",\n privacyPolicyUrl: app.privacyPolicyUrl || \"\",\n termsOfServiceUrl: app.termsOfServiceUrl || \"\",\n githubUrl: app.githubUrl || \"\",\n requiresApproval: app.requiresApproval,\n });\n setHasChanges(false);\n }\n // Re-sync form when the underlying application data changes (e.g. after\n // a refetch triggered by a mutation). We intentionally depend on\n // `data?.application` rather than the derived `app` object so that the\n // effect fires when the query cache updates.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [data?.application]);\n\n // ---------------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------------\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleChange = (\n field: keyof EditApplicationFormData,\n value: string | string[] | boolean,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n setHasChanges(true);\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const validateForm = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.appNameRequired\",\n \"Application name is required\",\n );\n } else if (formData.name.length < 3) {\n newErrors.name = tr(\n \"devportal.appNameMinLength\",\n \"Name must be at least 3 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.appNameMaxLength\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalidChars\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (!formData.shortDescription.trim()) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionRequired\",\n \"Short description is required\",\n );\n } else if (formData.shortDescription.length > 200) {\n newErrors.shortDescription = tr(\n \"devportal.shortDescriptionMaxLength\",\n \"Short description must be less than 200 characters\",\n );\n }\n\n if (!formData.appType) {\n newErrors.appType = tr(\n \"devportal.appTypeRequired\",\n \"Application type is required\",\n );\n }\n\n if (!formData.category) {\n newErrors.category = tr(\n \"devportal.categoryRequired\",\n \"Category is required\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm() || !app) {\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n name: formData.name,\n shortDescription: formData.shortDescription,\n description: formData.description,\n appType: formData.appType,\n visibility: formData.visibility,\n category: formData.category,\n tags: formData.tags,\n keywords: formData.keywords,\n websiteUrl: formData.websiteUrl || null,\n documentationUrl: formData.documentationUrl || null,\n supportUrl: formData.supportUrl || null,\n privacyPolicyUrl: formData.privacyPolicyUrl || null,\n termsOfServiceUrl: formData.termsOfServiceUrl || null,\n githubUrl: formData.githubUrl || null,\n },\n },\n });\n setHasChanges(false);\n } catch (err) {\n console.error(\"Failed to update application:\", err);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleDelete = async () => {\n if (!app) return;\n void nativeImpact(\"medium\");\n try {\n await deleteApplication({\n variables: { id: app.id },\n });\n void nativeNotify(\"success\");\n handleNavigate(\"/apps\");\n } catch (err) {\n void nativeNotify(\"error\");\n console.error(\"Failed to delete application:\", err);\n }\n };\n\n const handleDeprecate = async () => {\n if (!app) return;\n try {\n // Deprecate is modelled as a status transition via update. The backend\n // treats a visibility change to PRIVATE + status mutation or a dedicated\n // deprecation endpoint. Here we use submitApplication with a status note\n // as the deprecate action since there is no separate `deprecateApplication`\n // mutation. If a dedicated mutation is added later, swap it in here.\n await updateApplication({\n variables: {\n id: app.id,\n input: {\n visibility: \"PRIVATE\",\n },\n },\n });\n setShowDeprecateDialog(false);\n } catch (err) {\n console.error(\"Failed to deprecate application:\", err);\n }\n };\n\n const handleDeactivate = async () => {\n if (!app) return;\n try {\n await deactivateApp({\n variables: { id: app.id },\n });\n setShowDeactivateDialog(false);\n handleNavigate(\"/apps\");\n } catch (err) {\n console.error(\"Failed to deactivate application:\", err);\n }\n };\n\n const handleSubmitForReview = async () => {\n if (!app) return;\n try {\n setIsSubmitting(true);\n await submitApplication({\n variables: { id: app.id },\n });\n } catch (err) {\n console.error(\"Failed to submit for review:\", err);\n } finally {\n setIsSubmitting(false);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Loading / Error / Not-found states\n // ---------------------------------------------------------------------------\n if (loading) {\n return (\n <LoadingState\n message={tr(\"devportal.loadingApplication\", \"Loading application...\")}\n />\n );\n }\n\n if (queryError) {\n return (\n <PageLayout\n title={tr(\"devportal.error\", \"Error\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle size={48} className=\"text-destructive mb-4\" />\n <h3 className=\"text-lg font-semibold mb-2\">\n {tr(\n \"devportal.failedToLoadApplication\",\n \"Failed to load application\",\n )}\n </h3>\n <p className=\"text-sm text-muted-foreground\">{queryError.message}</p>\n </div>\n </PageLayout>\n );\n }\n\n if (!app) {\n return (\n <PageLayout\n title={tr(\"devportal.notFound\", \"Not Found\")}\n backLink={`${basePath}/apps`}\n backLabel={tr(\"devportal.backToApplications\", \"Back to Applications\")}\n >\n <EmptyState\n icon={<AppWindow size={48} />}\n title={tr(\"devportal.applicationNotFound\", \"Application not found\")}\n description={tr(\n \"devportal.applicationNotFoundDescription\",\n \"The application you're looking for doesn't exist or you don't have access to it.\",\n )}\n />\n </PageLayout>\n );\n }\n\n // ---------------------------------------------------------------------------\n // Derived flags\n // ---------------------------------------------------------------------------\n const canSubmitForReview =\n app.status === \"DRAFT\" || app.status === \"REJECTED\";\n const canDeprecate = app.status === \"PUBLISHED\";\n const canDeactivate = app.status === \"ACTIVE\" || app.status === \"PUBLISHED\";\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_UPDATE}>\n <PageLayout\n title={`${tr(\"devportal.editAppTitlePrefix\", \"Edit:\")} ${app.name}`}\n description={tr(\n \"devportal.editAppDescription\",\n \"Update your application settings\",\n )}\n backLink={`${basePath}/apps/${app.id}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={app.status} type=\"app\" />\n </div>\n }\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Status banner for published apps */}\n {app.status === \"PUBLISHED\" && (\n <div className=\"rounded-lg border border-border-subtle bg-status-warning-bg-subtle p-4 flex gap-3\">\n <AlertTriangle\n size={20}\n className=\"text-status-warning-text flex-shrink-0 mt-0.5\"\n />\n <div className=\"text-sm text-status-warning-text\">\n <p className=\"font-medium\">\n {tr(\n \"devportal.appIsPublished\",\n \"This application is published\",\n )}\n </p>\n <p className=\"mt-1 text-status-warning-text\">\n {tr(\n \"devportal.publishedAppChangesWarning\",\n \"Changes to published apps may require re-review. Major changes will put the app back into review status.\",\n )}\n </p>\n </div>\n </div>\n )}\n\n {/* Basic Information */}\n <PageSection\n title={tr(\"devportal.basicInformation\", \"Basic Information\")}\n description={tr(\n \"devportal.basicInformationDescription\",\n \"Core details about your application\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.applicationName\", \"Application Name\")}\n value={formData.name}\n onChange={(e) => handleChange(\"name\", e.target.value)}\n placeholder={tr(\n \"devportal.applicationNamePlaceholder\",\n \"My Awesome App\",\n )}\n required\n error={errors.name}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.slugPlaceholder\", \"my-awesome-app\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugChangeWarning\",\n \"Changing the slug may break existing integrations\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.shortDescription\", \"Short Description\")}\n value={formData.shortDescription}\n onChange={(e) =>\n handleChange(\"shortDescription\", e.target.value)\n }\n placeholder={tr(\n \"devportal.shortDescriptionPlaceholder\",\n \"A brief description of what your app does\",\n )}\n required\n error={errors.shortDescription}\n hint={`${formData.shortDescription.length}/200 ${tr(\"devportal.characters\", \"characters\")}`}\n rows={2}\n />\n\n <TextareaField\n label={tr(\"devportal.fullDescription\", \"Full Description\")}\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder={tr(\n \"devportal.fullDescriptionPlaceholder\",\n \"Detailed description of your application, features, and use cases...\",\n )}\n rows={6}\n hint={tr(\n \"devportal.markdownSupported\",\n \"Markdown formatting is supported\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Classification */}\n <PageSection\n title={tr(\"devportal.classification\", \"Classification\")}\n description={tr(\n \"devportal.classificationDescription\",\n \"Categorize your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <SelectField\n label={tr(\"devportal.applicationType\", \"Application Type\")}\n value={formData.appType}\n onChange={(e) => handleChange(\"appType\", e.target.value)}\n options={APP_TYPE_OPTIONS}\n required\n error={errors.appType}\n />\n\n <SelectField\n label={tr(\"devportal.category\", \"Category\")}\n value={formData.category}\n onChange={(e) => handleChange(\"category\", e.target.value)}\n options={CATEGORY_OPTIONS}\n required\n error={errors.category}\n />\n\n <SelectField\n label={tr(\"devportal.visibility\", \"Visibility\")}\n value={formData.visibility}\n onChange={(e) =>\n handleChange(\"visibility\", e.target.value as AppVisibility)\n }\n options={VISIBILITY_OPTIONS}\n />\n </div>\n\n <TagInput\n label={tr(\"devportal.tags\", \"Tags\")}\n value={formData.tags}\n onChange={(tags) => handleChange(\"tags\", tags)}\n placeholder={tr(\n \"devportal.addTagsPlaceholder\",\n \"Add tags (press Enter)\",\n )}\n />\n\n <TagInput\n label={tr(\"devportal.keywords\", \"Keywords\")}\n value={formData.keywords}\n onChange={(keywords) => handleChange(\"keywords\", keywords)}\n placeholder={tr(\n \"devportal.addKeywordsPlaceholder\",\n \"Add keywords (press Enter)\",\n )}\n />\n </PageSection>\n\n {/* URLs */}\n <PageSection\n title={tr(\"devportal.links\", \"Links\")}\n description={tr(\n \"devportal.linksDescription\",\n \"External resources for your application\",\n )}\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.websiteUrlPlaceholder\",\n \"https://example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.documentationUrl\", \"Documentation URL\")}\n type=\"url\"\n value={formData.documentationUrl}\n onChange={(e) =>\n handleChange(\"documentationUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.documentationUrlPlaceholder\",\n \"https://docs.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.supportUrl\", \"Support URL\")}\n type=\"url\"\n value={formData.supportUrl}\n onChange={(e) => handleChange(\"supportUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.supportUrlPlaceholder\",\n \"https://support.example.com\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.githubUrl\", \"GitHub URL\")}\n type=\"url\"\n value={formData.githubUrl}\n onChange={(e) => handleChange(\"githubUrl\", e.target.value)}\n placeholder={tr(\n \"devportal.githubUrlPlaceholder\",\n \"https://github.com/org/repo\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.privacyPolicyUrl\", \"Privacy Policy URL\")}\n type=\"url\"\n value={formData.privacyPolicyUrl}\n onChange={(e) =>\n handleChange(\"privacyPolicyUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.privacyPolicyUrlPlaceholder\",\n \"https://example.com/privacy\",\n )}\n />\n\n <InputField\n label={tr(\n \"devportal.termsOfServiceUrl\",\n \"Terms of Service URL\",\n )}\n type=\"url\"\n value={formData.termsOfServiceUrl}\n onChange={(e) =>\n handleChange(\"termsOfServiceUrl\", e.target.value)\n }\n placeholder={tr(\n \"devportal.termsOfServiceUrlPlaceholder\",\n \"https://example.com/terms\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Settings */}\n <PageSection\n title={tr(\"devportal.settings\", \"Settings\")}\n description={tr(\n \"devportal.settingsDescription\",\n \"Additional configuration options\",\n )}\n >\n <div className=\"space-y-4\">\n <CheckboxField\n label={tr(\n \"devportal.requireWorkspaceAdminApproval\",\n \"Require workspace admin approval\",\n )}\n description={tr(\n \"devportal.requireWorkspaceAdminApprovalDescription\",\n \"When enabled, workspace admins must approve installations before users can access this app\",\n )}\n checked={formData.requiresApproval}\n onChange={(e) =>\n handleChange(\"requiresApproval\", e.target.checked)\n }\n />\n </div>\n </PageSection>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-4 border-t pt-6\">\n {/* Primary actions */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n {canSubmitForReview && (\n <button\n type=\"button\"\n onClick={handleSubmitForReview}\n disabled={isSubmitting || hasChanges}\n className=\"inline-flex items-center gap-2 rounded-md border border-primary bg-primary/10 px-4 py-2 text-sm font-medium text-primary hover:bg-primary/20 transition-colors disabled:opacity-50\"\n >\n <Send size={16} />\n {tr(\"devportal.submitForReview\", \"Submit for Review\")}\n </button>\n )}\n </div>\n <button\n type=\"submit\"\n disabled={isSubmitting || !hasChanges || !canUpdate}\n aria-disabled={!canUpdate}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isSubmitting ? (\n <>\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.saving\", \"Saving...\")}\n </>\n ) : (\n <>\n <Save size={16} />\n {tr(\"devportal.saveChanges\", \"Save Changes\")}\n </>\n )}\n </button>\n </div>\n\n {/* Danger zone */}\n <div className=\"rounded-lg border border-border-subtle bg-status-error-bg-subtle p-4\">\n <h3 className=\"text-sm font-medium text-status-error-text\">\n {tr(\"devportal.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-error-text\">\n {tr(\n \"devportal.dangerZoneWarning\",\n \"These actions are irreversible. Please proceed with caution.\",\n )}\n </p>\n <div className=\"mt-4 flex gap-3\">\n {canDeprecate && (\n <button\n type=\"button\"\n onClick={() => setShowDeprecateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Archive size={14} />\n {tr(\"devportal.deprecateApp\", \"Deprecate App\")}\n </button>\n )}\n {canDeactivate && (\n <button\n type=\"button\"\n onClick={() => setShowDeactivateDialog(true)}\n className=\"inline-flex items-center gap-2 rounded-md border border-accent-orange bg-bg-surface px-3 py-1.5 text-sm font-medium text-accent-orange hover:bg-accent-orange-subtle transition-colors\"\n >\n <Power size={14} />\n {tr(\"devportal.deactivateApp\", \"Deactivate App\")}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setShowDeleteDialog(true)}\n disabled={!canDelete}\n aria-disabled={!canDelete}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg bg-bg-surface px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n <Trash2 size={14} />\n {tr(\"devportal.deleteApp\", \"Delete App\")}\n </button>\n </div>\n </div>\n </div>\n </form>\n\n {/* Delete confirmation dialog */}\n <ConfirmDialog\n open={showDeleteDialog}\n onOpenChange={setShowDeleteDialog}\n title={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n description={tr(\n \"devportal.deleteApplicationConfirm\",\n `Are you sure you want to delete \"${app.name}\"? This action cannot be undone. All OAuth clients, API keys, webhooks, and analytics data will be permanently deleted.`,\n )}\n confirmLabel={tr(\"devportal.deleteApplication\", \"Delete Application\")}\n variant=\"destructive\"\n onConfirm={handleDelete}\n />\n\n {/* Deprecate confirmation dialog */}\n <ConfirmDialog\n open={showDeprecateDialog}\n onOpenChange={setShowDeprecateDialog}\n title={tr(\"devportal.deprecateApplication\", \"Deprecate Application\")}\n description={tr(\n \"devportal.deprecateApplicationConfirm\",\n `Are you sure you want to deprecate \"${app.name}\"? Existing users will still be able to use the app, but it will no longer be visible in the marketplace and new installations will be disabled.`,\n )}\n confirmLabel={tr(\n \"devportal.deprecateApplication\",\n \"Deprecate Application\",\n )}\n variant=\"warning\"\n onConfirm={handleDeprecate}\n />\n\n {/* Deactivate confirmation dialog */}\n <ConfirmDialog\n open={showDeactivateDialog}\n onOpenChange={setShowDeactivateDialog}\n title={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n description={tr(\n \"devportal.deactivateApplicationConfirm\",\n `Are you sure you want to deactivate \"${app.name}\"? This will revoke all active tokens and disable the app from the auth-svc layer. The app will no longer be able to authenticate or act on behalf of users.`,\n )}\n confirmLabel={tr(\n \"devportal.deactivateApplication\",\n \"Deactivate Application\",\n )}\n variant=\"destructive\"\n onConfirm={handleDeactivate}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ var y = () => {
|
|
|
19
19
|
let { navigate: y, basePath: b } = e(), { t: x } = d(), S = (e, t) => {
|
|
20
20
|
let n = x(e);
|
|
21
21
|
return n === e ? t : n;
|
|
22
|
-
}, [C, w] = u(""), [T, E] = u(""), { data: D, loading: O, error: k, refetch: A } = o({ fetchPolicy: "cache-and-network" }), j = D?.currentUserApps?.map((e) => ({
|
|
22
|
+
}, [C, w] = u(""), [T, E] = u(""), { data: D, loading: O, error: k, refetch: A } = o({ fetchPolicy: "cache-and-network" }), j = D?.currentUserApps?.apps.map((e) => ({
|
|
23
23
|
id: e.id,
|
|
24
24
|
name: e.name,
|
|
25
25
|
description: e.description ?? void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthClientsPage.js","names":[],"sources":["../../../src/devportal/pages/OAuthClientsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { Plus, Search, Shield, Loader2, AlertCircle } from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { ActiveIndicator } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { AuthApp } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { useCurrentUserAppsQuery, getErrorMessage } from \"../hooks\";\n\n/**\n * OAuth Apps listing page\n *\n * Displays the current user's OAuth apps from auth-svc.\n * Previously showed devportal-svc OAuthClients per application;\n * now shows all user-owned apps centrally.\n */\nexport const OAuthClientsPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [typeFilter, setTypeFilter] = useState<string>(\"\");\n\n // Fetch apps from auth-svc (user-scoped, no applicationId needed)\n const { data, loading, error, refetch } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const apps: AuthApp[] =\n data?.currentUserApps?.map(\n (app: (typeof data.currentUserApps)[number]) => ({\n id: app.id,\n name: app.name,\n description: app.description ?? undefined,\n clientId: app.clientId,\n type: app.type as AuthApp[\"type\"],\n status: app.status as AuthApp[\"status\"],\n scopes: app.scopes ?? [],\n redirectUris: app.redirectUris ?? [],\n ownerUserId: app.ownerUserId,\n tenantId: app.tenantId ?? \"\",\n createdAt: app.createdAt,\n updatedAt: app.updatedAt,\n }),\n ) ?? [];\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Filter apps\n const filteredApps = apps.filter((app) => {\n const matchesSearch =\n !searchQuery ||\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n app.clientId.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesType = !typeFilter || app.type === typeFilter;\n return matchesSearch && matchesType;\n });\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.oauthApps\", \"OAuth Apps\")}\n description={tr(\n \"devportal.manageOAuthApps\",\n \"Manage your OAuth applications and credentials\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n <span className=\"ml-2 text-muted-foreground\">\n {tr(\"devportal.loadingApps\", \"Loading apps...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.oauthApps\", \"OAuth Apps\")}\n description={tr(\n \"devportal.manageOAuthApps\",\n \"Manage your OAuth applications and credentials\",\n )}\n >\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-destructive\" />\n <h3 className=\"mt-2 font-medium text-destructive\">\n {tr(\"devportal.failedToLoadApps\", \"Failed to load apps\")}\n </h3>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"name\",\n header: \"App\",\n render: (app: AuthApp) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n app.status === \"ACTIVE\"\n ? \"bg-primary/10 text-primary\"\n : \"bg-bg-sunken text-text-tertiary\",\n )}\n >\n <Shield size={20} />\n </div>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium\">{app.name}</p>\n <span\n className={cn(\n \"rounded px-1.5 py-0.5 text-xs\",\n app.type === \"SERVICE\"\n ? \"bg-accent-purple-subtle text-accent-purple\"\n : app.type === \"INTEGRATION\" ||\n app.type === \"API_INTEGRATION\"\n ? \"bg-status-info-bg-subtle text-status-info-text\"\n : \"bg-status-success-bg-subtle text-status-success-text\",\n )}\n >\n {app.type}\n </span>\n </div>\n <code className=\"text-xs text-muted-foreground\">\n {app.clientId}\n </code>\n </div>\n </div>\n ),\n },\n {\n key: \"scopes\",\n header: \"Scopes\",\n render: (app: AuthApp) => (\n <div className=\"flex flex-wrap gap-1\">\n {app.scopes.slice(0, 3).map((scope) => (\n <span\n key={scope}\n className=\"inline-flex rounded bg-muted px-1.5 py-0.5 text-xs\"\n >\n {scope}\n </span>\n ))}\n {app.scopes.length > 3 && (\n <span className=\"text-xs text-muted-foreground\">\n +{app.scopes.length - 3}\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"redirectUris\",\n header: \"Redirect URIs\",\n render: (app: AuthApp) => (\n <span className=\"text-sm\">\n {app.redirectUris.length} URI\n {app.redirectUris.length !== 1 ? \"s\" : \"\"}\n </span>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (app: AuthApp) => (\n <div className=\"flex items-center gap-2\">\n <ActiveIndicator active={app.status === \"ACTIVE\"} />\n <span className=\"text-sm\">{app.status}</span>\n </div>\n ),\n },\n ];\n\n // Stats\n const stats = {\n total: apps.length,\n active: apps.filter((a) => a.status === \"ACTIVE\").length,\n integration: apps.filter(\n (a) => a.type === \"INTEGRATION\" || a.type === \"API_INTEGRATION\",\n ).length,\n service: apps.filter((a) => a.type === \"SERVICE\").length,\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n title={tr(\"devportal.oauthApps\", \"OAuth Apps\")}\n description={tr(\n \"devportal.manageOAuthApps\",\n \"Manage your OAuth applications and credentials\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/oauth/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <Plus size={16} />\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 <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-muted-foreground\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchByNameOrClientId\",\n \"Search by name or client ID...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-background pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value)}\n className=\"rounded-md border bg-background 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 <option value=\"SERVICE\">\n {tr(\"devportal.service\", \"Service\")}\n </option>\n <option value=\"INTEGRATION\">\n {tr(\"devportal.integration\", \"Integration\")}\n </option>\n <option value=\"BOT\">{tr(\"devportal.bot\", \"Bot\")}</option>\n <option value=\"WEB_APP\">\n {tr(\"devportal.webApp\", \"Web App\")}\n </option>\n <option value=\"AUTOMATION\">\n {tr(\"devportal.automation\", \"Automation\")}\n </option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats */}\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.total}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.active\", \"Active\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.active}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-accent-blue\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.integration\", \"Integration\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.integration}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-accent-purple\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.service\", \"Service\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.service}</p>\n </div>\n </div>\n\n <p className=\"text-sm text-muted-foreground\">\n {filteredApps.length}{\" \"}\n {filteredApps.length !== 1\n ? tr(\"devportal.appsFound\", \"apps found\")\n : tr(\"devportal.appFound\", \"app found\")}\n </p>\n\n {/* Apps Table */}\n {filteredApps.length > 0 ? (\n <DataTable\n data={filteredApps}\n columns={columns}\n keyField=\"id\"\n onRowClick={(app) => handleNavigate(`/oauth/${app.id}`)}\n />\n ) : (\n <EmptyState\n icon={<Shield size={32} />}\n title={tr(\"devportal.noOAuthAppsFound\", \"No OAuth apps found\")}\n description={\n searchQuery || typeFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstOAuthApp\",\n \"Create your first OAuth app to enable authentication\",\n )\n }\n action={\n !searchQuery && !typeFilter ? (\n <button\n onClick={() => handleNavigate(\"/oauth/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,IAAa,UAA6B;CACxC,IAAM,EAAE,aAAU,gBAAa,GAAc,EACvC,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAE9B,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAY,KAAiB,EAAiB,GAAG,EAGlD,EAAE,SAAM,YAAS,UAAO,eAAY,EAAwB,EAChE,aAAa,qBACd,CAAC,EAEI,IACJ,GAAM,iBAAiB,KACpB,OAAgD;EAC/C,IAAI,EAAI;EACR,MAAM,EAAI;EACV,aAAa,EAAI,eAAe,KAAA;EAChC,UAAU,EAAI;EACd,MAAM,EAAI;EACV,QAAQ,EAAI;EACZ,QAAQ,EAAI,UAAU,EAAE;EACxB,cAAc,EAAI,gBAAgB,EAAE;EACpC,aAAa,EAAI;EACjB,UAAU,EAAI,YAAY;EAC1B,WAAW,EAAI;EACf,WAAW,EAAI;EAChB,EACF,IAAI,EAAE,EAEH,KAAkB,MAAiB;AACvC,MAAW,GAAG,IAAW,IAAO;IAI5B,IAAe,EAAK,QAAQ,MAAQ;EACxC,IAAM,IACJ,CAAC,KACD,EAAI,KAAK,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,IAC1D,EAAI,SAAS,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,EAC1D,IAAc,CAAC,KAAc,EAAI,SAAS;AAChD,SAAO,KAAiB;GACxB;AAGF,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,GAAD;EACE,OAAO,EAAG,uBAAuB,aAAa;EAC9C,aAAa,EACX,6BACA,iDACD;YAED,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA,EAClE,kBAAC,QAAD;IAAM,WAAU;cACb,EAAG,yBAAyB,kBAAkB;IAC1C,CAAA,CACH;;EACK,CAAA;AAKjB,KAAI,EACF,QACE,kBAAC,GAAD;EACE,OAAO,EAAG,uBAAuB,aAAa;EAC9C,aAAa,EACX,6BACA,iDACD;YAED,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA;IAC5D,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,sBAAsB;KACrD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAgB,EAAM;KACrB,CAAA;IACJ,kBAAC,UAAD;KACE,eAAe,GAAS;KACxB,WAAU;eAET,EAAG,sBAAsB,YAAY;KAC/B,CAAA;IACL;;EACK,CAAA;CAIjB,IAAM,IAAU;EACd;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KACE,WAAW,EACT,yDACA,EAAI,WAAW,WACX,+BACA,kCACL;eAED,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;KAChB,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAe,EAAI;MAAS,CAAA,EACzC,kBAAC,QAAD;MACE,WAAW,EACT,iCACA,EAAI,SAAS,YACT,+CACA,EAAI,SAAS,iBACX,EAAI,SAAS,oBACb,mDACA,uDACP;gBAEA,EAAI;MACA,CAAA,CACH;QACN,kBAAC,QAAD;KAAM,WAAU;eACb,EAAI;KACA,CAAA,CACH,EAAA,CAAA,CACF;;GAET;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAI,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,MAC3B,kBAAC,QAAD;KAEE,WAAU;eAET;KACI,EAJA,EAIA,CACP,EACD,EAAI,OAAO,SAAS,KACnB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAgD,KAC5C,EAAI,OAAO,SAAS,EACjB;OAEL;;GAET;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACG,EAAI,aAAa;KAAO;KACxB,EAAI,aAAa,WAAW,IAAU,KAAN;KAC5B;;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAiB,QAAQ,EAAI,WAAW,UAAY,CAAA,EACpD,kBAAC,QAAD;KAAM,WAAU;eAAW,EAAI;KAAc,CAAA,CACzC;;GAET;EACF,EAGK,IAAQ;EACZ,OAAO,EAAK;EACZ,QAAQ,EAAK,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC;EAClD,aAAa,EAAK,QACf,MAAM,EAAE,SAAS,iBAAiB,EAAE,SAAS,kBAC/C,CAAC;EACF,SAAS,EAAK,QAAQ,MAAM,EAAE,SAAS,UAAU,CAAC;EACnD;AAED,QACE,kBAAC,GAAD;EAAiB,YAAY,EAAsB;YACjD,kBAAC,GAAD;GACE,OAAO,EAAG,uBAAuB,aAAa;GAC9C,aAAa,EACX,6BACA,iDACD;GACD,SACE,kBAAC,UAAD;IACE,eAAe,EAAe,aAAa;IAC3C,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,uBAAuB,aAAa,CACjC;;aAbb;IAiBE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,MAAM;QACN,WAAU;QACV,CAAA,EACF,kBAAC,SAAD;QACE,MAAK;QACL,aAAa,EACX,oCACA,iCACD;QACD,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,WAAU;QACV,CAAA,CACE;UACN,kBAAC,UAAD;OACE,OAAO;OACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;OAC9C,WAAU;iBAHZ;QAKE,kBAAC,UAAD;SAAQ,OAAM;mBAAI,EAAG,sBAAsB,YAAY;SAAU,CAAA;QACjE,kBAAC,UAAD;SAAQ,OAAM;mBACX,EAAG,qBAAqB,UAAU;SAC5B,CAAA;QACT,kBAAC,UAAD;SAAQ,OAAM;mBACX,EAAG,yBAAyB,cAAc;SACpC,CAAA;QACT,kBAAC,UAAD;SAAQ,OAAM;mBAAO,EAAG,iBAAiB,MAAM;SAAU,CAAA;QACzD,kBAAC,UAAD;SAAQ,OAAM;mBACX,EAAG,oBAAoB,UAAU;SAC3B,CAAA;QACT,kBAAC,UAAD;SAAQ,OAAM;mBACX,EAAG,wBAAwB,aAAa;SAClC,CAAA;QACF;SACL;;KACF,CAAA;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACpB,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAG,uBAAuB,aAAa;SACnC,CAAA,CACH;WACN,kBAAC,KAAD;QAAG,WAAU;kBAA+B,EAAM;QAAU,CAAA,CACxD;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACpB,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAG,oBAAoB,SAAS;SAC5B,CAAA,CACH;WACN,kBAAC,KAAD;QAAG,WAAU;kBAA+B,EAAM;QAAW,CAAA,CACzD;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACpB,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAG,yBAAyB,cAAc;SACtC,CAAA,CACH;WACN,kBAAC,KAAD;QAAG,WAAU;kBAA+B,EAAM;QAAgB,CAAA,CAC9D;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACpB,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAG,qBAAqB,UAAU;SAC9B,CAAA,CACH;WACN,kBAAC,KAAD;QAAG,WAAU;kBAA+B,EAAM;QAAY,CAAA,CAC1D;;MACF;;IAEN,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAa;MAAQ;MACrB,EAAa,WAAW,IAErB,EAAG,sBAAsB,YAAY,GADrC,EAAG,uBAAuB,aAAa;MAEzC;;IAGH,EAAa,SAAS,IACrB,kBAAC,GAAD;KACE,MAAM;KACG;KACT,UAAS;KACT,aAAa,MAAQ,EAAe,UAAU,EAAI,KAAK;KACvD,CAAA,GAEF,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;KAC1B,OAAO,EAAG,8BAA8B,sBAAsB;KAC9D,aACE,KAAe,IACX,EACE,iCACA,6BACD,GACD,EACE,iCACA,uDACD;KAEP,QACE,CAAC,KAAe,CAAC,IACf,kBAAC,UAAD;MACE,eAAe,EAAe,aAAa;MAC3C,WAAU;gBAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,uBAAuB,aAAa,CACjC;UACP,KAAA;KAEN,CAAA;IAEO;;EACG,CAAA"}
|
|
1
|
+
{"version":3,"file":"OAuthClientsPage.js","names":[],"sources":["../../../src/devportal/pages/OAuthClientsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { Plus, Search, Shield, Loader2, AlertCircle } from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { ActiveIndicator } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { AuthApp } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { useCurrentUserAppsQuery, getErrorMessage } from \"../hooks\";\n\n/**\n * OAuth Apps listing page\n *\n * Displays the current user's OAuth apps from auth-svc.\n * Previously showed devportal-svc OAuthClients per application;\n * now shows all user-owned apps centrally.\n */\nexport const OAuthClientsPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [typeFilter, setTypeFilter] = useState<string>(\"\");\n\n // Fetch apps from auth-svc (user-scoped, no applicationId needed)\n const { data, loading, error, refetch } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const apps: AuthApp[] =\n data?.currentUserApps?.apps.map(\n (app: (typeof data.currentUserApps.apps)[number]) => ({\n id: app.id,\n name: app.name,\n description: app.description ?? undefined,\n clientId: app.clientId,\n type: app.type as AuthApp[\"type\"],\n status: app.status as AuthApp[\"status\"],\n scopes: app.scopes ?? [],\n redirectUris: app.redirectUris ?? [],\n ownerUserId: app.ownerUserId,\n tenantId: app.tenantId ?? \"\",\n createdAt: app.createdAt,\n updatedAt: app.updatedAt,\n }),\n ) ?? [];\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Filter apps\n const filteredApps = apps.filter((app) => {\n const matchesSearch =\n !searchQuery ||\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n app.clientId.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesType = !typeFilter || app.type === typeFilter;\n return matchesSearch && matchesType;\n });\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.oauthApps\", \"OAuth Apps\")}\n description={tr(\n \"devportal.manageOAuthApps\",\n \"Manage your OAuth applications and credentials\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n <span className=\"ml-2 text-muted-foreground\">\n {tr(\"devportal.loadingApps\", \"Loading apps...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.oauthApps\", \"OAuth Apps\")}\n description={tr(\n \"devportal.manageOAuthApps\",\n \"Manage your OAuth applications and credentials\",\n )}\n >\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-destructive\" />\n <h3 className=\"mt-2 font-medium text-destructive\">\n {tr(\"devportal.failedToLoadApps\", \"Failed to load apps\")}\n </h3>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"name\",\n header: \"App\",\n render: (app: AuthApp) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n app.status === \"ACTIVE\"\n ? \"bg-primary/10 text-primary\"\n : \"bg-bg-sunken text-text-tertiary\",\n )}\n >\n <Shield size={20} />\n </div>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium\">{app.name}</p>\n <span\n className={cn(\n \"rounded px-1.5 py-0.5 text-xs\",\n app.type === \"SERVICE\"\n ? \"bg-accent-purple-subtle text-accent-purple\"\n : app.type === \"INTEGRATION\" ||\n app.type === \"API_INTEGRATION\"\n ? \"bg-status-info-bg-subtle text-status-info-text\"\n : \"bg-status-success-bg-subtle text-status-success-text\",\n )}\n >\n {app.type}\n </span>\n </div>\n <code className=\"text-xs text-muted-foreground\">\n {app.clientId}\n </code>\n </div>\n </div>\n ),\n },\n {\n key: \"scopes\",\n header: \"Scopes\",\n render: (app: AuthApp) => (\n <div className=\"flex flex-wrap gap-1\">\n {app.scopes.slice(0, 3).map((scope) => (\n <span\n key={scope}\n className=\"inline-flex rounded bg-muted px-1.5 py-0.5 text-xs\"\n >\n {scope}\n </span>\n ))}\n {app.scopes.length > 3 && (\n <span className=\"text-xs text-muted-foreground\">\n +{app.scopes.length - 3}\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"redirectUris\",\n header: \"Redirect URIs\",\n render: (app: AuthApp) => (\n <span className=\"text-sm\">\n {app.redirectUris.length} URI\n {app.redirectUris.length !== 1 ? \"s\" : \"\"}\n </span>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (app: AuthApp) => (\n <div className=\"flex items-center gap-2\">\n <ActiveIndicator active={app.status === \"ACTIVE\"} />\n <span className=\"text-sm\">{app.status}</span>\n </div>\n ),\n },\n ];\n\n // Stats\n const stats = {\n total: apps.length,\n active: apps.filter((a) => a.status === \"ACTIVE\").length,\n integration: apps.filter(\n (a) => a.type === \"INTEGRATION\" || a.type === \"API_INTEGRATION\",\n ).length,\n service: apps.filter((a) => a.type === \"SERVICE\").length,\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n title={tr(\"devportal.oauthApps\", \"OAuth Apps\")}\n description={tr(\n \"devportal.manageOAuthApps\",\n \"Manage your OAuth applications and credentials\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/oauth/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n <Plus size={16} />\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 <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-muted-foreground\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchByNameOrClientId\",\n \"Search by name or client ID...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-background pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value)}\n className=\"rounded-md border bg-background 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 <option value=\"SERVICE\">\n {tr(\"devportal.service\", \"Service\")}\n </option>\n <option value=\"INTEGRATION\">\n {tr(\"devportal.integration\", \"Integration\")}\n </option>\n <option value=\"BOT\">{tr(\"devportal.bot\", \"Bot\")}</option>\n <option value=\"WEB_APP\">\n {tr(\"devportal.webApp\", \"Web App\")}\n </option>\n <option value=\"AUTOMATION\">\n {tr(\"devportal.automation\", \"Automation\")}\n </option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats */}\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.total}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.active\", \"Active\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.active}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-accent-blue\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.integration\", \"Integration\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.integration}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center gap-2 text-accent-purple\">\n <Shield size={16} />\n <span className=\"text-sm\">\n {tr(\"devportal.service\", \"Service\")}\n </span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold\">{stats.service}</p>\n </div>\n </div>\n\n <p className=\"text-sm text-muted-foreground\">\n {filteredApps.length}{\" \"}\n {filteredApps.length !== 1\n ? tr(\"devportal.appsFound\", \"apps found\")\n : tr(\"devportal.appFound\", \"app found\")}\n </p>\n\n {/* Apps Table */}\n {filteredApps.length > 0 ? (\n <DataTable\n data={filteredApps}\n columns={columns}\n keyField=\"id\"\n onRowClick={(app) => handleNavigate(`/oauth/${app.id}`)}\n />\n ) : (\n <EmptyState\n icon={<Shield size={32} />}\n title={tr(\"devportal.noOAuthAppsFound\", \"No OAuth apps found\")}\n description={\n searchQuery || typeFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstOAuthApp\",\n \"Create your first OAuth app to enable authentication\",\n )\n }\n action={\n !searchQuery && !typeFilter ? (\n <button\n onClick={() => handleNavigate(\"/oauth/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createApp\", \"Create App\")}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -40,7 +40,7 @@ var x = (e, t) => {
|
|
|
40
40
|
description: o(D)
|
|
41
41
|
})
|
|
42
42
|
});
|
|
43
|
-
let M = T?.myDeveloperProfile, N = T?.myDeveloperOrganizations ?? [], P = T?.myApiKeys ?? [], F = P[0],
|
|
43
|
+
let M = T?.myDeveloperProfile, N = T?.myDeveloperOrganizations ?? [], P = T?.myApiKeys?.apiKeys ?? [], F = T?.myApiKeys?.totalCount ?? P.length, I = P[0], L = N.reduce((e, t) => e + (t.totalRevenue ?? 0), 0);
|
|
44
44
|
return /* @__PURE__ */ y(n, {
|
|
45
45
|
title: A,
|
|
46
46
|
description: j,
|
|
@@ -147,11 +147,11 @@ var x = (e, t) => {
|
|
|
147
147
|
}),
|
|
148
148
|
/* @__PURE__ */ y("p", {
|
|
149
149
|
className: "mt-2 text-2xl font-semibold",
|
|
150
|
-
children:
|
|
150
|
+
children: F
|
|
151
151
|
}),
|
|
152
152
|
/* @__PURE__ */ y("p", {
|
|
153
153
|
className: "mt-1 text-sm text-muted-foreground",
|
|
154
|
-
children:
|
|
154
|
+
children: I ? `${w("devportal.settings.lastUsed", "Last used")} ${x(I.lastUsedAt, k)}` : w("devportal.settings.noApiKeys", "No API keys created yet")
|
|
155
155
|
})
|
|
156
156
|
]
|
|
157
157
|
}), /* @__PURE__ */ b("button", {
|
|
@@ -204,7 +204,7 @@ var x = (e, t) => {
|
|
|
204
204
|
className: "mt-1 text-sm text-muted-foreground",
|
|
205
205
|
children: [
|
|
206
206
|
"$",
|
|
207
|
-
|
|
207
|
+
L.toLocaleString(),
|
|
208
208
|
" ",
|
|
209
209
|
w("devportal.settings.lifetimeRevenue", "lifetime revenue across all teams")
|
|
210
210
|
]
|
|
@@ -292,16 +292,16 @@ var x = (e, t) => {
|
|
|
292
292
|
title: w("devportal.settings.recentCredentialActivity", "Recent credential activity"),
|
|
293
293
|
children: /* @__PURE__ */ y("div", {
|
|
294
294
|
className: "rounded-lg border bg-card p-6",
|
|
295
|
-
children:
|
|
295
|
+
children: I ? /* @__PURE__ */ b("div", {
|
|
296
296
|
className: "space-y-4 text-sm",
|
|
297
297
|
children: [/* @__PURE__ */ b("div", {
|
|
298
298
|
className: "flex items-center justify-between gap-3",
|
|
299
299
|
children: [/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
|
|
300
300
|
className: "font-medium",
|
|
301
|
-
children:
|
|
301
|
+
children: I.name
|
|
302
302
|
}), /* @__PURE__ */ y("p", {
|
|
303
303
|
className: "text-muted-foreground",
|
|
304
|
-
children:
|
|
304
|
+
children: I.prefix
|
|
305
305
|
})] }), /* @__PURE__ */ y("span", {
|
|
306
306
|
className: "inline-flex items-center rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text",
|
|
307
307
|
children: w("devportal.common.active", "Active")
|
|
@@ -311,15 +311,15 @@ var x = (e, t) => {
|
|
|
311
311
|
children: [
|
|
312
312
|
/* @__PURE__ */ b("div", {
|
|
313
313
|
className: "flex items-center justify-between gap-3",
|
|
314
|
-
children: [/* @__PURE__ */ y("dt", { children: w("devportal.common.created", "Created") }), /* @__PURE__ */ y("dd", { children: x(
|
|
314
|
+
children: [/* @__PURE__ */ y("dt", { children: w("devportal.common.created", "Created") }), /* @__PURE__ */ y("dd", { children: x(I.createdAt, k) })]
|
|
315
315
|
}),
|
|
316
316
|
/* @__PURE__ */ b("div", {
|
|
317
317
|
className: "flex items-center justify-between gap-3",
|
|
318
|
-
children: [/* @__PURE__ */ y("dt", { children: w("devportal.common.expires", "Expires") }), /* @__PURE__ */ y("dd", { children: x(
|
|
318
|
+
children: [/* @__PURE__ */ y("dt", { children: w("devportal.common.expires", "Expires") }), /* @__PURE__ */ y("dd", { children: x(I.expiresAt, k) })]
|
|
319
319
|
}),
|
|
320
320
|
/* @__PURE__ */ b("div", {
|
|
321
321
|
className: "flex items-center justify-between gap-3",
|
|
322
|
-
children: [/* @__PURE__ */ y("dt", { children: w("devportal.settings.lastUsed", "Last used") }), /* @__PURE__ */ y("dd", { children: x(
|
|
322
|
+
children: [/* @__PURE__ */ y("dt", { children: w("devportal.settings.lastUsed", "Last used") }), /* @__PURE__ */ y("dd", { children: x(I.lastUsedAt, k) })]
|
|
323
323
|
})
|
|
324
324
|
]
|
|
325
325
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsPage.js","names":[],"sources":["../../../src/devportal/pages/SettingsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport {\n Building2,\n ExternalLink,\n KeyRound,\n Mail,\n Settings,\n Shield,\n UserCircle2,\n Webhook,\n Workflow,\n Loader2,\n AlertCircle,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { VerifiedBadge } from \"../components/StatusBadge\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n getErrorMessage,\n useGetDeveloperPortalSettingsOverviewQuery,\n} from \"../hooks\";\nimport { useTr } from \"../utils/i18n\";\n\nconst formatDate = (\n value: string | null | undefined,\n neverLabel: string,\n): string => {\n if (!value) return neverLabel;\n\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return neverLabel;\n\n return parsed.toLocaleDateString();\n};\n\nexport const SettingsPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const tr = useTr();\n const { data, loading, error } = useGetDeveloperPortalSettingsOverviewQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const neverLabel = tr(\"devportal.settings.never\", \"Never\");\n const title = tr(\"devportal.settings.title\", \"Settings\");\n const description = tr(\n \"devportal.settings.description\",\n \"Review your developer portal account, credentials, and support options\",\n );\n\n if (loading && !data) {\n return (\n <PageLayout title={title} description={description}>\n <div\n className=\"flex items-center justify-center py-16 text-muted-foreground\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={tr(\"devportal.common.loading\", \"Loading\")}\n >\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title={title} description={description}>\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\"devportal.settings.errorTitle\", \"Unable to load settings\")}\n description={getErrorMessage(error)}\n />\n </PageLayout>\n );\n }\n\n const profile = data?.myDeveloperProfile;\n const organizations = data?.myDeveloperOrganizations ?? [];\n const apiKeys = data?.myApiKeys ?? [];\n const latestApiKey = apiKeys[0];\n const totalRevenue = organizations.reduce(\n (sum: number, organization: (typeof organizations)[number]) =>\n sum + (organization.totalRevenue ?? 0),\n 0,\n );\n\n return (\n <PageLayout title={title} description={description}>\n <div className=\"grid gap-6 xl:grid-cols-[minmax(0,2fr)_minmax(320px,1fr)]\">\n <div className=\"space-y-6\">\n <PageSection title={tr(\"devportal.settings.profile\", \"Profile\")}>\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <UserCircle2\n className=\"h-5 w-5 text-primary\"\n aria-hidden=\"true\"\n />\n <h2 className=\"text-lg font-semibold\">\n {tr(\n \"devportal.settings.developerProfile\",\n \"Developer profile\",\n )}\n </h2>\n {profile?.isVerified && <VerifiedBadge verified />}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {profile?.companyName ||\n tr(\n \"devportal.settings.noCompanyName\",\n \"No company name set yet\",\n )}\n </p>\n {profile?.bio && (\n <p className=\"max-w-2xl text-sm text-muted-foreground\">\n {profile.bio}\n </p>\n )}\n <div className=\"flex flex-wrap gap-2 pt-1 text-xs text-muted-foreground\">\n <span className=\"rounded-full bg-muted px-2.5 py-1\">\n {profile?.applicationCount ?? 0}{\" \"}\n {tr(\n \"devportal.settings.publishedAssetsTracked\",\n \"published assets tracked\",\n )}\n </span>\n <span className=\"rounded-full bg-muted px-2.5 py-1\">\n {profile?.totalInstalls ?? 0}{\" \"}\n {tr(\"devportal.settings.installs\", \"installs\")}\n </span>\n <span className=\"rounded-full bg-muted px-2.5 py-1\">\n {tr(\"devportal.settings.emailUpdates\", \"Email updates\")}{\" \"}\n {profile?.emailNotifications\n ? tr(\"devportal.common.enabled\", \"enabled\")\n : tr(\"devportal.common.disabled\", \"disabled\")}\n </span>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/publisher/profile\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Settings size={16} aria-hidden=\"true\" />\n {tr(\"devportal.settings.manageProfile\", \"Manage profile\")}\n </button>\n </div>\n </div>\n </PageSection>\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <PageSection\n title={tr(\"devportal.settings.credentials\", \"Credentials\")}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/api-keys\")}\n className=\"rounded-lg border bg-background p-4 text-left transition-colors hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <div className=\"flex items-center gap-2 text-primary\">\n <KeyRound size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\"devportal.apiKeys\", \"API Keys\")}\n </span>\n </div>\n <p className=\"mt-2 text-2xl font-semibold\">\n {apiKeys.length}\n </p>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {latestApiKey\n ? `${tr(\"devportal.settings.lastUsed\", \"Last used\")} ${formatDate(latestApiKey.lastUsedAt, neverLabel)}`\n : tr(\n \"devportal.settings.noApiKeys\",\n \"No API keys created yet\",\n )}\n </p>\n </button>\n\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/oauth\")}\n className=\"rounded-lg border bg-background p-4 text-left transition-colors hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <div className=\"flex items-center gap-2 text-primary\">\n <Workflow size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\"devportal.settings.oauthApps\", \"OAuth Apps\")}\n </span>\n </div>\n <p className=\"mt-2 text-2xl font-semibold\">\n {tr(\"devportal.common.open\", \"Open\")}\n </p>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr(\n \"devportal.settings.oauthDescription\",\n \"Manage OAuth clients on the dedicated credentials page.\",\n )}\n </p>\n </button>\n </div>\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.settings.organizations\", \"Organizations\")}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Building2 size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\n \"devportal.settings.developerOrganizations\",\n \"Developer organizations\",\n )}\n </span>\n </div>\n <p className=\"mt-2 text-2xl font-semibold\">\n {organizations.length}\n </p>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n ${totalRevenue.toLocaleString()}{\" \"}\n {tr(\n \"devportal.settings.lifetimeRevenue\",\n \"lifetime revenue across all teams\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\n \"devportal.settings.viewOrganizations\",\n \"View organizations\",\n )}\n <ExternalLink size={14} aria-hidden=\"true\" />\n </button>\n </div>\n </PageSection>\n </div>\n\n <PageSection\n title={tr(\n \"devportal.settings.deliveryAutomation\",\n \"Delivery & automation\",\n )}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <div className=\"flex items-center gap-2 text-primary\">\n <Webhook size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\n \"devportal.settings.webhookOperations\",\n \"Webhook operations\",\n )}\n </span>\n </div>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {tr(\n \"devportal.settings.webhookOperationsDescription\",\n \"Configure application webhooks from each application detail page. Delivery history and retry status are available per app.\",\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/webhooks\")}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\"devportal.settings.openWebhooks\", \"Open webhooks\")}\n <ExternalLink size={14} aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n </PageSection>\n </div>\n\n <div className=\"space-y-6\">\n <PageSection\n title={tr(\n \"devportal.settings.securitySupport\",\n \"Security & support\",\n )}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Shield size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\"devportal.settings.securityPosture\", \"Security posture\")}\n </span>\n </div>\n <ul className=\"mt-4 space-y-3 text-sm text-muted-foreground\">\n <li className=\"rounded-md bg-muted px-3 py-2\">\n {tr(\n \"devportal.settings.securityNote1\",\n \"API keys and OAuth apps are managed through the public global gateway.\",\n )}\n </li>\n <li className=\"rounded-md bg-muted px-3 py-2\">\n {tr(\n \"devportal.settings.securityNote2\",\n \"Webhook testing and delivery history are available per application.\",\n )}\n </li>\n <li className=\"rounded-md bg-muted px-3 py-2\">\n {tr(\n \"devportal.settings.securityNote3\",\n \"Profile verification status is reflected directly from your developer profile.\",\n )}\n </li>\n </ul>\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\n \"devportal.settings.recentCredentialActivity\",\n \"Recent credential activity\",\n )}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n {latestApiKey ? (\n <div className=\"space-y-4 text-sm\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"font-medium\">{latestApiKey.name}</p>\n <p className=\"text-muted-foreground\">\n {latestApiKey.prefix}\n </p>\n </div>\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {tr(\"devportal.common.active\", \"Active\")}\n </span>\n </div>\n <dl className=\"grid gap-3 text-muted-foreground\">\n <div className=\"flex items-center justify-between gap-3\">\n <dt>{tr(\"devportal.common.created\", \"Created\")}</dt>\n <dd>{formatDate(latestApiKey.createdAt, neverLabel)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-3\">\n <dt>{tr(\"devportal.common.expires\", \"Expires\")}</dt>\n <dd>{formatDate(latestApiKey.expiresAt, neverLabel)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-3\">\n <dt>{tr(\"devportal.settings.lastUsed\", \"Last used\")}</dt>\n <dd>{formatDate(latestApiKey.lastUsedAt, neverLabel)}</dd>\n </div>\n </dl>\n </div>\n ) : (\n <EmptyState\n icon={<KeyRound size={24} />}\n title={tr(\n \"devportal.settings.noCredentialActivity\",\n \"No credential activity yet\",\n )}\n description={tr(\n \"devportal.settings.noCredentialActivityDescription\",\n \"Create an API key or OAuth app to manage credentials from here.\",\n )}\n />\n )}\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.settings.accountActions\", \"Account actions\")}\n >\n <div className=\"rounded-lg border border-status-warning-border bg-status-warning-bg-subtle p-6\">\n <h2 className=\"font-semibold text-status-warning-text\">\n {tr(\n \"devportal.settings.needHelpTitle\",\n \"Need help with account changes?\",\n )}\n </h2>\n <p className=\"mt-2 text-sm text-status-warning-text\">\n {tr(\n \"devportal.settings.needHelpDescription\",\n \"Destructive account operations are handled through support so the team can verify ownership and preserve billing history, audit trails, and marketplace assets.\",\n )}\n </p>\n <a\n href=\"mailto:support@burdenoff.com?subject=Developer%20portal%20account%20support\"\n className=\"mt-4 inline-flex items-center gap-2 rounded-md border border-status-warning-border bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Mail size={16} aria-hidden=\"true\" />\n {tr(\"devportal.settings.contactSupport\", \"Contact support\")}\n </a>\n </div>\n </PageSection>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SettingsPage.js","names":[],"sources":["../../../src/devportal/pages/SettingsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport {\n Building2,\n ExternalLink,\n KeyRound,\n Mail,\n Settings,\n Shield,\n UserCircle2,\n Webhook,\n Workflow,\n Loader2,\n AlertCircle,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { VerifiedBadge } from \"../components/StatusBadge\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n getErrorMessage,\n useGetDeveloperPortalSettingsOverviewQuery,\n} from \"../hooks\";\nimport { useTr } from \"../utils/i18n\";\n\nconst formatDate = (\n value: string | null | undefined,\n neverLabel: string,\n): string => {\n if (!value) return neverLabel;\n\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return neverLabel;\n\n return parsed.toLocaleDateString();\n};\n\nexport const SettingsPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const tr = useTr();\n const { data, loading, error } = useGetDeveloperPortalSettingsOverviewQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const neverLabel = tr(\"devportal.settings.never\", \"Never\");\n const title = tr(\"devportal.settings.title\", \"Settings\");\n const description = tr(\n \"devportal.settings.description\",\n \"Review your developer portal account, credentials, and support options\",\n );\n\n if (loading && !data) {\n return (\n <PageLayout title={title} description={description}>\n <div\n className=\"flex items-center justify-center py-16 text-muted-foreground\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={tr(\"devportal.common.loading\", \"Loading\")}\n >\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout title={title} description={description}>\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\"devportal.settings.errorTitle\", \"Unable to load settings\")}\n description={getErrorMessage(error)}\n />\n </PageLayout>\n );\n }\n\n const profile = data?.myDeveloperProfile;\n const organizations = data?.myDeveloperOrganizations ?? [];\n const apiKeys = data?.myApiKeys?.apiKeys ?? [];\n const totalApiKeyCount = data?.myApiKeys?.totalCount ?? apiKeys.length;\n const latestApiKey = apiKeys[0];\n const totalRevenue = organizations.reduce(\n (sum: number, organization: (typeof organizations)[number]) =>\n sum + (organization.totalRevenue ?? 0),\n 0,\n );\n\n return (\n <PageLayout title={title} description={description}>\n <div className=\"grid gap-6 xl:grid-cols-[minmax(0,2fr)_minmax(320px,1fr)]\">\n <div className=\"space-y-6\">\n <PageSection title={tr(\"devportal.settings.profile\", \"Profile\")}>\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <UserCircle2\n className=\"h-5 w-5 text-primary\"\n aria-hidden=\"true\"\n />\n <h2 className=\"text-lg font-semibold\">\n {tr(\n \"devportal.settings.developerProfile\",\n \"Developer profile\",\n )}\n </h2>\n {profile?.isVerified && <VerifiedBadge verified />}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {profile?.companyName ||\n tr(\n \"devportal.settings.noCompanyName\",\n \"No company name set yet\",\n )}\n </p>\n {profile?.bio && (\n <p className=\"max-w-2xl text-sm text-muted-foreground\">\n {profile.bio}\n </p>\n )}\n <div className=\"flex flex-wrap gap-2 pt-1 text-xs text-muted-foreground\">\n <span className=\"rounded-full bg-muted px-2.5 py-1\">\n {profile?.applicationCount ?? 0}{\" \"}\n {tr(\n \"devportal.settings.publishedAssetsTracked\",\n \"published assets tracked\",\n )}\n </span>\n <span className=\"rounded-full bg-muted px-2.5 py-1\">\n {profile?.totalInstalls ?? 0}{\" \"}\n {tr(\"devportal.settings.installs\", \"installs\")}\n </span>\n <span className=\"rounded-full bg-muted px-2.5 py-1\">\n {tr(\"devportal.settings.emailUpdates\", \"Email updates\")}{\" \"}\n {profile?.emailNotifications\n ? tr(\"devportal.common.enabled\", \"enabled\")\n : tr(\"devportal.common.disabled\", \"disabled\")}\n </span>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/publisher/profile\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Settings size={16} aria-hidden=\"true\" />\n {tr(\"devportal.settings.manageProfile\", \"Manage profile\")}\n </button>\n </div>\n </div>\n </PageSection>\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <PageSection\n title={tr(\"devportal.settings.credentials\", \"Credentials\")}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/api-keys\")}\n className=\"rounded-lg border bg-background p-4 text-left transition-colors hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <div className=\"flex items-center gap-2 text-primary\">\n <KeyRound size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\"devportal.apiKeys\", \"API Keys\")}\n </span>\n </div>\n <p className=\"mt-2 text-2xl font-semibold\">\n {totalApiKeyCount}\n </p>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {latestApiKey\n ? `${tr(\"devportal.settings.lastUsed\", \"Last used\")} ${formatDate(latestApiKey.lastUsedAt, neverLabel)}`\n : tr(\n \"devportal.settings.noApiKeys\",\n \"No API keys created yet\",\n )}\n </p>\n </button>\n\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/oauth\")}\n className=\"rounded-lg border bg-background p-4 text-left transition-colors hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <div className=\"flex items-center gap-2 text-primary\">\n <Workflow size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\"devportal.settings.oauthApps\", \"OAuth Apps\")}\n </span>\n </div>\n <p className=\"mt-2 text-2xl font-semibold\">\n {tr(\"devportal.common.open\", \"Open\")}\n </p>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr(\n \"devportal.settings.oauthDescription\",\n \"Manage OAuth clients on the dedicated credentials page.\",\n )}\n </p>\n </button>\n </div>\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.settings.organizations\", \"Organizations\")}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Building2 size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\n \"devportal.settings.developerOrganizations\",\n \"Developer organizations\",\n )}\n </span>\n </div>\n <p className=\"mt-2 text-2xl font-semibold\">\n {organizations.length}\n </p>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n ${totalRevenue.toLocaleString()}{\" \"}\n {tr(\n \"devportal.settings.lifetimeRevenue\",\n \"lifetime revenue across all teams\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\n \"devportal.settings.viewOrganizations\",\n \"View organizations\",\n )}\n <ExternalLink size={14} aria-hidden=\"true\" />\n </button>\n </div>\n </PageSection>\n </div>\n\n <PageSection\n title={tr(\n \"devportal.settings.deliveryAutomation\",\n \"Delivery & automation\",\n )}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <div className=\"flex items-center gap-2 text-primary\">\n <Webhook size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\n \"devportal.settings.webhookOperations\",\n \"Webhook operations\",\n )}\n </span>\n </div>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {tr(\n \"devportal.settings.webhookOperationsDescription\",\n \"Configure application webhooks from each application detail page. Delivery history and retry status are available per app.\",\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/webhooks\")}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\"devportal.settings.openWebhooks\", \"Open webhooks\")}\n <ExternalLink size={14} aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n </PageSection>\n </div>\n\n <div className=\"space-y-6\">\n <PageSection\n title={tr(\n \"devportal.settings.securitySupport\",\n \"Security & support\",\n )}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Shield size={16} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr(\"devportal.settings.securityPosture\", \"Security posture\")}\n </span>\n </div>\n <ul className=\"mt-4 space-y-3 text-sm text-muted-foreground\">\n <li className=\"rounded-md bg-muted px-3 py-2\">\n {tr(\n \"devportal.settings.securityNote1\",\n \"API keys and OAuth apps are managed through the public global gateway.\",\n )}\n </li>\n <li className=\"rounded-md bg-muted px-3 py-2\">\n {tr(\n \"devportal.settings.securityNote2\",\n \"Webhook testing and delivery history are available per application.\",\n )}\n </li>\n <li className=\"rounded-md bg-muted px-3 py-2\">\n {tr(\n \"devportal.settings.securityNote3\",\n \"Profile verification status is reflected directly from your developer profile.\",\n )}\n </li>\n </ul>\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\n \"devportal.settings.recentCredentialActivity\",\n \"Recent credential activity\",\n )}\n >\n <div className=\"rounded-lg border bg-card p-6\">\n {latestApiKey ? (\n <div className=\"space-y-4 text-sm\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"font-medium\">{latestApiKey.name}</p>\n <p className=\"text-muted-foreground\">\n {latestApiKey.prefix}\n </p>\n </div>\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {tr(\"devportal.common.active\", \"Active\")}\n </span>\n </div>\n <dl className=\"grid gap-3 text-muted-foreground\">\n <div className=\"flex items-center justify-between gap-3\">\n <dt>{tr(\"devportal.common.created\", \"Created\")}</dt>\n <dd>{formatDate(latestApiKey.createdAt, neverLabel)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-3\">\n <dt>{tr(\"devportal.common.expires\", \"Expires\")}</dt>\n <dd>{formatDate(latestApiKey.expiresAt, neverLabel)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-3\">\n <dt>{tr(\"devportal.settings.lastUsed\", \"Last used\")}</dt>\n <dd>{formatDate(latestApiKey.lastUsedAt, neverLabel)}</dd>\n </div>\n </dl>\n </div>\n ) : (\n <EmptyState\n icon={<KeyRound size={24} />}\n title={tr(\n \"devportal.settings.noCredentialActivity\",\n \"No credential activity yet\",\n )}\n description={tr(\n \"devportal.settings.noCredentialActivityDescription\",\n \"Create an API key or OAuth app to manage credentials from here.\",\n )}\n />\n )}\n </div>\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.settings.accountActions\", \"Account actions\")}\n >\n <div className=\"rounded-lg border border-status-warning-border bg-status-warning-bg-subtle p-6\">\n <h2 className=\"font-semibold text-status-warning-text\">\n {tr(\n \"devportal.settings.needHelpTitle\",\n \"Need help with account changes?\",\n )}\n </h2>\n <p className=\"mt-2 text-sm text-status-warning-text\">\n {tr(\n \"devportal.settings.needHelpDescription\",\n \"Destructive account operations are handled through support so the team can verify ownership and preserve billing history, audit trails, and marketplace assets.\",\n )}\n </p>\n <a\n href=\"mailto:support@burdenoff.com?subject=Developer%20portal%20account%20support\"\n className=\"mt-4 inline-flex items-center gap-2 rounded-md border border-status-warning-border bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Mail size={16} aria-hidden=\"true\" />\n {tr(\"devportal.settings.contactSupport\", \"Contact support\")}\n </a>\n </div>\n </PageSection>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-devportal",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.530.1",
|
|
4
4
|
"description": "Burdenoff Developer Portal Microfrontend - Complete developer operations interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@apollo/client": "^4.0.11",
|
|
46
|
-
"@burdenoff/fe-libs": "2026.
|
|
46
|
+
"@burdenoff/fe-libs": "2026.529.1",
|
|
47
47
|
"@tanstack/react-query": "^5.90.16",
|
|
48
48
|
"clsx": "^2.1.1",
|
|
49
49
|
"date-fns": "^3.6.0",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"@graphql-codegen/typescript": "^4.0.0",
|
|
66
66
|
"@graphql-codegen/typescript-operations": "^4.0.0",
|
|
67
67
|
"@graphql-codegen/typescript-react-apollo": "^4.0.0",
|
|
68
|
+
"@playwright/test": "^1.59.1",
|
|
68
69
|
"@testing-library/dom": "^10.4.1",
|
|
69
70
|
"@testing-library/jest-dom": "^6.9.1",
|
|
70
71
|
"@testing-library/react": "15.0.7",
|