@burdenoff/microfe-devportal 2026.830.1 → 2026.831.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/AdminDevportalRoot.d.ts.map +1 -1
- package/dist/devportal/AdminDevportalRoot.js +16 -19
- package/dist/devportal/AdminDevportalRoot.js.map +1 -1
- package/dist/devportal/DevportalRoot.d.ts.map +1 -1
- package/dist/devportal/DevportalRoot.js +36 -39
- package/dist/devportal/DevportalRoot.js.map +1 -1
- package/dist/devportal/components/AppCard.js +41 -41
- package/dist/devportal/components/ConfirmDialog.d.ts.map +1 -1
- package/dist/devportal/components/ConfirmDialog.js +19 -22
- package/dist/devportal/components/ConfirmDialog.js.map +1 -1
- package/dist/devportal/components/DataTable.d.ts.map +1 -1
- package/dist/devportal/components/DataTable.js +31 -34
- package/dist/devportal/components/DataTable.js.map +1 -1
- package/dist/devportal/components/FeatureFlaggedRoute.js +8 -8
- package/dist/devportal/components/FormField.d.ts.map +1 -1
- package/dist/devportal/components/FormField.js +26 -32
- package/dist/devportal/components/FormField.js.map +1 -1
- package/dist/devportal/components/GrantDelegationModal.js +64 -64
- package/dist/devportal/components/GrantDelegationModal.js.map +1 -1
- package/dist/devportal/components/LoadingState.d.ts.map +1 -1
- package/dist/devportal/components/LoadingState.js +5 -8
- package/dist/devportal/components/LoadingState.js.map +1 -1
- package/dist/devportal/components/PageLayout.d.ts.map +1 -1
- package/dist/devportal/components/PageLayout.js +10 -13
- package/dist/devportal/components/PageLayout.js.map +1 -1
- package/dist/devportal/components/PermissionGuard.d.ts.map +1 -1
- package/dist/devportal/components/PermissionGuard.js +11 -14
- package/dist/devportal/components/PermissionGuard.js.map +1 -1
- package/dist/devportal/components/SecretDisplay.d.ts.map +1 -1
- package/dist/devportal/components/SecretDisplay.js +24 -30
- package/dist/devportal/components/SecretDisplay.js.map +1 -1
- package/dist/devportal/components/SectionNav.d.ts.map +1 -1
- package/dist/devportal/components/SectionNav.js +24 -27
- package/dist/devportal/components/SectionNav.js.map +1 -1
- package/dist/devportal/components/StatusBadge.d.ts.map +1 -1
- package/dist/devportal/components/StatusBadge.js +24 -33
- package/dist/devportal/components/StatusBadge.js.map +1 -1
- package/dist/devportal/index.d.ts +1 -0
- package/dist/devportal/index.d.ts.map +1 -1
- package/dist/devportal/index.js +1 -0
- package/dist/devportal/pages/AdminReviewPage.js +172 -172
- package/dist/devportal/pages/AnalyticsDashboardPage.js +43 -43
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +119 -119
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +45 -45
- package/dist/devportal/pages/ApplicationDetailPage.js +607 -607
- package/dist/devportal/pages/ApplicationVersionsPage.js +13 -13
- package/dist/devportal/pages/ApplicationsPage.d.ts.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +104 -101
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +45 -45
- package/dist/devportal/pages/CreateApplicationPage.js +38 -38
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +52 -52
- package/dist/devportal/pages/CreateOrganizationPage.js +26 -26
- package/dist/devportal/pages/CreateWebhookPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreateWebhookPage.js +48 -48
- package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
- package/dist/devportal/pages/DashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/DashboardPage.js +66 -72
- package/dist/devportal/pages/DashboardPage.js.map +1 -1
- package/dist/devportal/pages/DelegationsPage.js +174 -174
- package/dist/devportal/pages/DocumentationPage.js +93 -93
- package/dist/devportal/pages/EditApplicationPage.js +14 -14
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/ListingPreviewPage.js +76 -76
- package/dist/devportal/pages/OAuthClientDetailPage.js +75 -75
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +64 -64
- package/dist/devportal/pages/OrganizationDetailPage.js +189 -189
- package/dist/devportal/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationMembersPage.js +10 -10
- package/dist/devportal/pages/OrganizationsPage.d.ts.map +1 -1
- package/dist/devportal/pages/OrganizationsPage.js +163 -154
- package/dist/devportal/pages/OrganizationsPage.js.map +1 -1
- package/dist/devportal/pages/PublishPage.js +104 -104
- package/dist/devportal/pages/PublishPage.js.map +1 -1
- package/dist/devportal/pages/PublisherProfilePage.js +55 -55
- package/dist/devportal/pages/ResourcesPage.js +60 -60
- package/dist/devportal/pages/RevenueDashboardPage.js +60 -60
- package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js +83 -83
- package/dist/devportal/pages/SellerItemsPage.js +163 -163
- package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
- package/dist/devportal/pages/SellerOrdersPage.js +63 -63
- package/dist/devportal/pages/SettingsPage.js +21 -21
- package/dist/devportal/pages/SetupPage.js +183 -183
- package/dist/devportal/pages/SetupPage.js.map +1 -1
- package/dist/devportal/pages/StoreSubmissionPage.js +5 -5
- package/dist/devportal/pages/WebhookDetailPage.js +123 -123
- package/dist/devportal/pages/WebhookDetailPage.js.map +1 -1
- package/dist/devportal/pages/WebhooksPage.js +118 -118
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/dist/devportal/utils/i18n.d.ts +9 -2
- package/dist/devportal/utils/i18n.d.ts.map +1 -1
- package/dist/devportal/utils/i18n.js +13 -10
- package/dist/devportal/utils/i18n.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/translations/en.d.ts +1251 -0
- package/dist/translations/en.d.ts.map +1 -0
- package/dist/translations/en.js +1235 -0
- package/dist/translations/en.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormField.js","names":[],"sources":["../../../src/devportal/components/FormField.tsx"],"sourcesContent":["import type {\n FC,\n InputHTMLAttributes,\n TextareaHTMLAttributes,\n SelectHTMLAttributes,\n ReactNode,\n} from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { cn } from \"../utils/cn\";\n\ninterface BaseFieldProps {\n label: string;\n error?: string;\n hint?: string;\n required?: boolean;\n className?: string;\n}\n\ninterface InputFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\"> {\n type?: \"text\" | \"email\" | \"password\" | \"number\" | \"url\" | \"tel\";\n}\n\ninterface TextareaFieldProps\n extends\n BaseFieldProps,\n Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, \"className\"> {}\n\ninterface SelectFieldProps\n extends\n BaseFieldProps,\n Omit<SelectHTMLAttributes<HTMLSelectElement>, \"className\"> {\n options: Array<{ value: string; label: string }>;\n placeholder?: string;\n}\n\ninterface CheckboxFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\" | \"type\"> {\n description?: string;\n}\n\n/**\n * Text input field with label and error handling\n */\nexport const InputField: FC<InputFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n type = \"text\",\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <input\n id={fieldId}\n type={type}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Textarea field with label and error handling\n */\nexport const TextareaField: FC<TextareaFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n id,\n rows = 4,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <textarea\n id={fieldId}\n rows={rows}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary resize-none\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Select field with label and error handling\n */\nexport const SelectField: FC<SelectFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n options,\n placeholder,\n id,\n ...props\n}) => {\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 resolvedPlaceholder =\n placeholder ?? tr(\"devportal.selectAnOption\", \"Select an option\");\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <select\n id={fieldId}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n >\n <option value=\"\">{resolvedPlaceholder}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Checkbox field with label and description\n */\nexport const CheckboxField: FC<CheckboxFieldProps> = ({\n label,\n description,\n error,\n className,\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"flex items-start gap-3\", className)}>\n <input\n id={fieldId}\n type=\"checkbox\"\n className={cn(\n \"mt-1 h-4 w-4 rounded border-border-default text-primary\",\n \"focus:ring-primary\",\n )}\n {...props}\n />\n <div>\n <label htmlFor={fieldId} className=\"text-sm font-medium cursor-pointer\">\n {label}\n </label>\n {description && (\n <p className=\"text-xs text-text-secondary\">{description}</p>\n )}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n </div>\n );\n};\n\n/**\n * Multi-select checkbox group\n */\nexport const CheckboxGroup: FC<{\n label: string;\n options: Array<{ value: string; label: string; description?: string }>;\n value: string[];\n onChange: (values: string[]) => void;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, options, value, onChange, error, hint, className }) => {\n const handleChange = (optionValue: string, checked: boolean) => {\n if (checked) {\n onChange([...value, optionValue]);\n } else {\n onChange(value.filter((v) => v !== optionValue));\n }\n };\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"space-y-2\">\n {options.map((option) => (\n <CheckboxField\n key={option.value}\n label={option.label}\n description={option.description}\n checked={value.includes(option.value)}\n onChange={(e) => handleChange(option.value, e.target.checked)}\n />\n ))}\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Tag input for multiple values\n */\nexport const TagInput: FC<{\n label: string;\n value: string[];\n onChange: (values: string[]) => void;\n placeholder?: string;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, value, onChange, placeholder, error, hint, className }) => {\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 handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\" || e.key === \",\") {\n e.preventDefault();\n const input = e.currentTarget;\n const newValue = input.value.trim();\n if (newValue && !value.includes(newValue)) {\n onChange([...value, newValue]);\n input.value = \"\";\n }\n }\n };\n\n const removeTag = (tagToRemove: string) => {\n onChange(value.filter((tag) => tag !== tagToRemove));\n };\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"flex flex-wrap gap-2 rounded-md border bg-bg-surface p-2\">\n {value.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-1 text-xs\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n className=\"hover:text-status-error-text\"\n >\n ×\n </button>\n </span>\n ))}\n <input\n type=\"text\"\n placeholder={\n value.length === 0\n ? placeholder\n : tr(\"devportal.addMore\", \"Add more...\")\n }\n onKeyDown={handleKeyDown}\n className=\"flex-1 min-w-[100px] bg-transparent text-sm outline-none\"\n />\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FormField.js","names":[],"sources":["../../../src/devportal/components/FormField.tsx"],"sourcesContent":["import type {\n FC,\n InputHTMLAttributes,\n TextareaHTMLAttributes,\n SelectHTMLAttributes,\n} from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\n\ninterface BaseFieldProps {\n label: string;\n error?: string;\n hint?: string;\n required?: boolean;\n className?: string;\n}\n\ninterface InputFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\"> {\n type?: \"text\" | \"email\" | \"password\" | \"number\" | \"url\" | \"tel\";\n}\n\ninterface TextareaFieldProps\n extends\n BaseFieldProps,\n Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, \"className\"> {}\n\ninterface SelectFieldProps\n extends\n BaseFieldProps,\n Omit<SelectHTMLAttributes<HTMLSelectElement>, \"className\"> {\n options: Array<{ value: string; label: string }>;\n placeholder?: string;\n}\n\ninterface CheckboxFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\" | \"type\"> {\n description?: string;\n}\n\n/**\n * Text input field with label and error handling\n */\nexport const InputField: FC<InputFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n type = \"text\",\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <input\n id={fieldId}\n type={type}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Textarea field with label and error handling\n */\nexport const TextareaField: FC<TextareaFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n id,\n rows = 4,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <textarea\n id={fieldId}\n rows={rows}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary resize-none\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Select field with label and error handling\n */\nexport const SelectField: FC<SelectFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n options,\n placeholder,\n id,\n ...props\n}) => {\n const tr = useTr();\n const resolvedPlaceholder =\n placeholder ?? tr(\"devportal.selectAnOption\", \"Select an option\");\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <select\n id={fieldId}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n >\n <option value=\"\">{resolvedPlaceholder}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Checkbox field with label and description\n */\nexport const CheckboxField: FC<CheckboxFieldProps> = ({\n label,\n description,\n error,\n className,\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"flex items-start gap-3\", className)}>\n <input\n id={fieldId}\n type=\"checkbox\"\n className={cn(\n \"mt-1 h-4 w-4 rounded border-border-default text-primary\",\n \"focus:ring-primary\",\n )}\n {...props}\n />\n <div>\n <label htmlFor={fieldId} className=\"text-sm font-medium cursor-pointer\">\n {label}\n </label>\n {description && (\n <p className=\"text-xs text-text-secondary\">{description}</p>\n )}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n </div>\n );\n};\n\n/**\n * Multi-select checkbox group\n */\nexport const CheckboxGroup: FC<{\n label: string;\n options: Array<{ value: string; label: string; description?: string }>;\n value: string[];\n onChange: (values: string[]) => void;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, options, value, onChange, error, hint, className }) => {\n const handleChange = (optionValue: string, checked: boolean) => {\n if (checked) {\n onChange([...value, optionValue]);\n } else {\n onChange(value.filter((v) => v !== optionValue));\n }\n };\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"space-y-2\">\n {options.map((option) => (\n <CheckboxField\n key={option.value}\n label={option.label}\n description={option.description}\n checked={value.includes(option.value)}\n onChange={(e) => handleChange(option.value, e.target.checked)}\n />\n ))}\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Tag input for multiple values\n */\nexport const TagInput: FC<{\n label: string;\n value: string[];\n onChange: (values: string[]) => void;\n placeholder?: string;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, value, onChange, placeholder, error, hint, className }) => {\n const tr = useTr();\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\" || e.key === \",\") {\n e.preventDefault();\n const input = e.currentTarget;\n const newValue = input.value.trim();\n if (newValue && !value.includes(newValue)) {\n onChange([...value, newValue]);\n input.value = \"\";\n }\n }\n };\n\n const removeTag = (tagToRemove: string) => {\n onChange(value.filter((tag) => tag !== tagToRemove));\n };\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"flex flex-wrap gap-2 rounded-md border bg-bg-surface p-2\">\n {value.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-1 text-xs\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n className=\"hover:text-status-error-text\"\n >\n ×\n </button>\n </span>\n ))}\n <input\n type=\"text\"\n placeholder={\n value.length === 0\n ? placeholder\n : tr(\"devportal.addMore\", \"Add more...\")\n }\n onKeyDown={handleKeyDown}\n className=\"flex-1 min-w-[100px] bg-transparent text-sm outline-none\"\n />\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -2,12 +2,12 @@ import { cn as e } from "../utils/cn.js";
|
|
|
2
2
|
import { useGrantDelegationMutation as t } from "../../generated/global-operations.js";
|
|
3
3
|
import { useMyWorkspacesQuery as n } from "../../generated/wspace-operations.js";
|
|
4
4
|
import { useCallback as r, useEffect as i, useId as a, useRef as o, useState as s } from "react";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { useGatewayClient as
|
|
5
|
+
import { AlertCircle as c, Check as l, CheckCircle as u, Loader2 as d, Search as ee, Shield as f, X as p } from "lucide-react";
|
|
6
|
+
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
7
|
+
import { useI18n as te } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
8
|
+
import { useGatewayClient as ne } from "@burdenoff/fe-libs/shared/graphql";
|
|
9
9
|
//#region src/devportal/components/GrantDelegationModal.tsx
|
|
10
|
-
function
|
|
10
|
+
function re(e) {
|
|
11
11
|
let t = /* @__PURE__ */ new Map();
|
|
12
12
|
for (let n of e) {
|
|
13
13
|
let [e] = n.split(":"), r = e || "other";
|
|
@@ -18,7 +18,7 @@ function g(e) {
|
|
|
18
18
|
var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSelectedWorkspaceIds: S, preSelectedScopes: C, onSuccess: w }) => {
|
|
19
19
|
let T = a(), E = a(), D = o(null), O = o(null), [k, A] = s(C ?? []), [j, M] = s(S ?? []), [N, P] = s(!1), [F, I] = s(""), [L, R] = s(""), [z, B] = s(null), [V, H] = s(null), U = (() => {
|
|
20
20
|
try {
|
|
21
|
-
return
|
|
21
|
+
return ne("workspace");
|
|
22
22
|
} catch {
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
@@ -36,7 +36,7 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
36
36
|
})) ?? [], [q, { loading: J }] = t({
|
|
37
37
|
refetchQueries: ["CurrentUserDelegations"],
|
|
38
38
|
awaitRefetchQueries: !0
|
|
39
|
-
}), { t: Y } =
|
|
39
|
+
}), { t: Y } = te(), X = (e, t) => {
|
|
40
40
|
let n = Y(e);
|
|
41
41
|
return n === e ? t : n;
|
|
42
42
|
};
|
|
@@ -78,7 +78,7 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
78
78
|
M((t) => t.includes(e) ? t.filter((t) => t !== e) : [...t, e]), P(!1);
|
|
79
79
|
}, []), oe = r(() => {
|
|
80
80
|
P((e) => (e || M([]), !e));
|
|
81
|
-
}, []), $ = K.filter((e) => e.name.toLowerCase().includes(F.toLowerCase())), se =
|
|
81
|
+
}, []), $ = K.filter((e) => e.name.toLowerCase().includes(F.toLowerCase())), se = re(x), ce = r(async () => {
|
|
82
82
|
if (B(null), k.length === 0) {
|
|
83
83
|
B(X("devportal.selectAtLeastOneScope", "Please select at least one scope to grant."));
|
|
84
84
|
return;
|
|
@@ -127,14 +127,14 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
127
127
|
q,
|
|
128
128
|
w
|
|
129
129
|
]);
|
|
130
|
-
return _ ? /* @__PURE__ */
|
|
130
|
+
return _ ? /* @__PURE__ */ g("div", {
|
|
131
131
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
132
|
-
children: [/* @__PURE__ */
|
|
132
|
+
children: [/* @__PURE__ */ h("button", {
|
|
133
133
|
type: "button",
|
|
134
134
|
"aria-label": "Close delegation dialog backdrop",
|
|
135
135
|
className: "absolute inset-0 bg-bg-overlay",
|
|
136
136
|
onClick: Q
|
|
137
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ g("div", {
|
|
138
138
|
ref: D,
|
|
139
139
|
role: "dialog",
|
|
140
140
|
"aria-modal": "true",
|
|
@@ -143,26 +143,26 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
143
143
|
tabIndex: -1,
|
|
144
144
|
className: "relative z-10 w-full max-w-lg rounded-lg bg-bg-surface shadow-xl",
|
|
145
145
|
children: [
|
|
146
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ g("div", {
|
|
147
147
|
className: "flex items-start gap-4 border-b p-6",
|
|
148
148
|
children: [
|
|
149
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ h("div", {
|
|
150
150
|
className: "rounded-full bg-status-success-bg-subtle p-2",
|
|
151
|
-
children: /* @__PURE__ */
|
|
151
|
+
children: /* @__PURE__ */ h(f, { className: "h-6 w-6 text-status-success-text" })
|
|
152
152
|
}),
|
|
153
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ g("div", {
|
|
154
154
|
className: "flex-1",
|
|
155
|
-
children: [/* @__PURE__ */
|
|
155
|
+
children: [/* @__PURE__ */ h("h3", {
|
|
156
156
|
id: T,
|
|
157
157
|
className: "text-lg font-semibold",
|
|
158
158
|
children: "Grant Delegation"
|
|
159
|
-
}), /* @__PURE__ */
|
|
159
|
+
}), /* @__PURE__ */ g("p", {
|
|
160
160
|
id: E,
|
|
161
161
|
className: "mt-1 text-sm text-text-secondary",
|
|
162
162
|
children: [
|
|
163
163
|
"Allow",
|
|
164
164
|
" ",
|
|
165
|
-
/* @__PURE__ */
|
|
165
|
+
/* @__PURE__ */ h("span", {
|
|
166
166
|
className: "font-medium text-text-primary",
|
|
167
167
|
children: b
|
|
168
168
|
}),
|
|
@@ -171,35 +171,35 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
171
171
|
]
|
|
172
172
|
})]
|
|
173
173
|
}),
|
|
174
|
-
/* @__PURE__ */
|
|
174
|
+
/* @__PURE__ */ h("button", {
|
|
175
175
|
type: "button",
|
|
176
176
|
onClick: Q,
|
|
177
177
|
disabled: J,
|
|
178
178
|
className: "rounded-md p-1 text-text-secondary hover:bg-bg-sunken transition-colors disabled:opacity-50",
|
|
179
|
-
children: /* @__PURE__ */ p
|
|
179
|
+
children: /* @__PURE__ */ h(p, { className: "h-5 w-5" })
|
|
180
180
|
})
|
|
181
181
|
]
|
|
182
182
|
}),
|
|
183
|
-
/* @__PURE__ */
|
|
183
|
+
/* @__PURE__ */ g("div", {
|
|
184
184
|
className: "space-y-5 p-6",
|
|
185
|
-
children: [V && /* @__PURE__ */
|
|
185
|
+
children: [V && /* @__PURE__ */ g("div", {
|
|
186
186
|
className: "flex items-start gap-3 rounded-md border border-border-subtle bg-status-success-bg-subtle p-4",
|
|
187
|
-
children: [/* @__PURE__ */
|
|
187
|
+
children: [/* @__PURE__ */ h(u, { className: "h-5 w-5 shrink-0 text-status-success-text" }), /* @__PURE__ */ g("div", {
|
|
188
188
|
className: "flex-1",
|
|
189
|
-
children: [/* @__PURE__ */
|
|
189
|
+
children: [/* @__PURE__ */ h("p", {
|
|
190
190
|
className: "text-sm font-medium text-status-success-text",
|
|
191
191
|
children: V
|
|
192
|
-
}), /* @__PURE__ */
|
|
192
|
+
}), /* @__PURE__ */ h("button", {
|
|
193
193
|
type: "button",
|
|
194
194
|
onClick: Q,
|
|
195
195
|
className: "mt-2 text-sm font-medium text-status-success-text hover:underline",
|
|
196
196
|
children: X("devportal.close", "Close")
|
|
197
197
|
})]
|
|
198
198
|
})]
|
|
199
|
-
}), !V && /* @__PURE__ */ m
|
|
200
|
-
/* @__PURE__ */
|
|
199
|
+
}), !V && /* @__PURE__ */ g(m, { children: [
|
|
200
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
|
|
201
201
|
className: "mb-2 block text-sm font-medium",
|
|
202
|
-
children: [X("devportal.permissionsScopes", "Permissions (Scopes)"), /* @__PURE__ */
|
|
202
|
+
children: [X("devportal.permissionsScopes", "Permissions (Scopes)"), /* @__PURE__ */ g("span", {
|
|
203
203
|
className: "ml-1.5 text-xs font-normal text-text-secondary",
|
|
204
204
|
children: [
|
|
205
205
|
k.length,
|
|
@@ -211,41 +211,41 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
211
211
|
X("devportal.selected", "selected")
|
|
212
212
|
]
|
|
213
213
|
})]
|
|
214
|
-
}), x.length === 0 ? /* @__PURE__ */
|
|
214
|
+
}), x.length === 0 ? /* @__PURE__ */ h("p", {
|
|
215
215
|
className: "text-sm text-text-secondary",
|
|
216
216
|
children: "This app has no scopes available."
|
|
217
|
-
}) : /* @__PURE__ */
|
|
217
|
+
}) : /* @__PURE__ */ g("div", {
|
|
218
218
|
className: "space-y-3",
|
|
219
|
-
children: [/* @__PURE__ */
|
|
219
|
+
children: [/* @__PURE__ */ g("div", {
|
|
220
220
|
className: "flex gap-2 text-xs",
|
|
221
221
|
children: [
|
|
222
|
-
/* @__PURE__ */
|
|
222
|
+
/* @__PURE__ */ h("button", {
|
|
223
223
|
type: "button",
|
|
224
224
|
onClick: () => A([...x]),
|
|
225
225
|
className: "text-status-success-text hover:underline",
|
|
226
226
|
children: X("devportal.selectAll", "Select all")
|
|
227
227
|
}),
|
|
228
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ h("button", {
|
|
229
229
|
type: "button",
|
|
230
230
|
onClick: () => A(x.filter((e) => e.includes(":read") || e.includes(":list"))),
|
|
231
231
|
className: "text-status-success-text hover:underline",
|
|
232
232
|
children: X("devportal.readOnly", "Read-only")
|
|
233
233
|
}),
|
|
234
|
-
/* @__PURE__ */
|
|
234
|
+
/* @__PURE__ */ h("button", {
|
|
235
235
|
type: "button",
|
|
236
236
|
onClick: () => A([]),
|
|
237
237
|
className: "text-text-secondary hover:underline",
|
|
238
238
|
children: X("devportal.clear", "Clear")
|
|
239
239
|
})
|
|
240
240
|
]
|
|
241
|
-
}), [...se.entries()].map(([t, n]) => /* @__PURE__ */
|
|
241
|
+
}), [...se.entries()].map(([t, n]) => /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("p", {
|
|
242
242
|
className: "mb-1 text-xs font-semibold uppercase text-text-secondary",
|
|
243
243
|
children: t
|
|
244
|
-
}), /* @__PURE__ */
|
|
244
|
+
}), /* @__PURE__ */ h("div", {
|
|
245
245
|
className: "flex flex-wrap gap-1.5",
|
|
246
246
|
children: n.map((t) => {
|
|
247
247
|
let n = k.includes(t), r = t.split(":")[1] || t, i = r.includes("write") || r.includes("create") || r.includes("delete") || r.includes("admin");
|
|
248
|
-
return /* @__PURE__ */
|
|
248
|
+
return /* @__PURE__ */ h("button", {
|
|
249
249
|
type: "button",
|
|
250
250
|
onClick: () => ie(t),
|
|
251
251
|
className: e("rounded-md px-2.5 py-1 text-xs font-medium transition-colors", n ? i ? "bg-status-warning-bg text-white" : "bg-status-success-bg text-white" : "border bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80"),
|
|
@@ -254,22 +254,22 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
254
254
|
})
|
|
255
255
|
})] }, t))]
|
|
256
256
|
})] }),
|
|
257
|
-
/* @__PURE__ */
|
|
258
|
-
/* @__PURE__ */
|
|
257
|
+
/* @__PURE__ */ g("div", { children: [
|
|
258
|
+
/* @__PURE__ */ h("label", {
|
|
259
259
|
className: "mb-2 block text-sm font-medium",
|
|
260
260
|
children: X("devportal.workspaceAccess", "Workspace Access")
|
|
261
261
|
}),
|
|
262
|
-
/* @__PURE__ */
|
|
262
|
+
/* @__PURE__ */ g("button", {
|
|
263
263
|
type: "button",
|
|
264
264
|
onClick: oe,
|
|
265
265
|
disabled: J,
|
|
266
266
|
className: e("mb-2 flex w-full items-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors", N ? "border-status-success-bg bg-status-success-bg-subtle text-status-success-text" : "hover:bg-bg-sunken"),
|
|
267
|
-
children: [N && /* @__PURE__ */
|
|
267
|
+
children: [N && /* @__PURE__ */ h(l, { className: "h-4 w-4 text-status-success-text" }), /* @__PURE__ */ h("span", { children: X("devportal.allWorkspacesUnrestricted", "All workspaces (unrestricted)") })]
|
|
268
268
|
}),
|
|
269
|
-
!N && /* @__PURE__ */ m
|
|
270
|
-
/* @__PURE__ */
|
|
269
|
+
!N && /* @__PURE__ */ g(m, { children: [
|
|
270
|
+
/* @__PURE__ */ g("div", {
|
|
271
271
|
className: "relative mb-2",
|
|
272
|
-
children: [/* @__PURE__ */
|
|
272
|
+
children: [/* @__PURE__ */ h(ee, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ h("input", {
|
|
273
273
|
type: "text",
|
|
274
274
|
value: F,
|
|
275
275
|
onChange: (e) => I(e.target.value),
|
|
@@ -278,35 +278,35 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
278
278
|
className: "block w-full rounded-md border py-2 pl-9 pr-3 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50"
|
|
279
279
|
})]
|
|
280
280
|
}),
|
|
281
|
-
/* @__PURE__ */
|
|
281
|
+
/* @__PURE__ */ h("div", {
|
|
282
282
|
className: "max-h-40 overflow-y-auto rounded-md border",
|
|
283
|
-
children: G ? /* @__PURE__ */
|
|
283
|
+
children: G ? /* @__PURE__ */ g("div", {
|
|
284
284
|
className: "flex items-center justify-center p-4",
|
|
285
|
-
children: [/* @__PURE__ */
|
|
285
|
+
children: [/* @__PURE__ */ h(d, { className: "h-4 w-4 animate-spin text-text-secondary" }), /* @__PURE__ */ h("span", {
|
|
286
286
|
className: "ml-2 text-sm text-text-secondary",
|
|
287
287
|
children: X("devportal.loadingWorkspaces", "Loading workspaces...")
|
|
288
288
|
})]
|
|
289
|
-
}) : $.length === 0 ? /* @__PURE__ */
|
|
289
|
+
}) : $.length === 0 ? /* @__PURE__ */ h("p", {
|
|
290
290
|
className: "p-4 text-center text-sm text-text-secondary",
|
|
291
291
|
children: K.length === 0 ? X("devportal.noWorkspacesFound", "No workspaces found") : X("devportal.noMatchingWorkspaces", "No matching workspaces")
|
|
292
292
|
}) : $.map((t) => {
|
|
293
293
|
let n = j.includes(t.id);
|
|
294
|
-
return /* @__PURE__ */
|
|
294
|
+
return /* @__PURE__ */ g("button", {
|
|
295
295
|
type: "button",
|
|
296
296
|
onClick: () => ae(t.id),
|
|
297
297
|
disabled: J,
|
|
298
298
|
className: e("flex w-full items-center gap-2 border-b px-3 py-2 text-left text-sm transition-colors last:border-b-0", n ? "bg-status-success-bg-subtle text-status-success-text" : "hover:bg-bg-sunken"),
|
|
299
|
-
children: [/* @__PURE__ */
|
|
299
|
+
children: [/* @__PURE__ */ h("div", {
|
|
300
300
|
className: e("flex h-4 w-4 shrink-0 items-center justify-center rounded border", n ? "border-status-success-bg bg-status-success-bg" : "border-muted-foreground/30"),
|
|
301
|
-
children: n && /* @__PURE__ */
|
|
302
|
-
}), /* @__PURE__ */
|
|
301
|
+
children: n && /* @__PURE__ */ h(l, { className: "h-3 w-3 text-white" })
|
|
302
|
+
}), /* @__PURE__ */ h("span", {
|
|
303
303
|
className: "font-medium",
|
|
304
304
|
children: t.name
|
|
305
305
|
})]
|
|
306
306
|
}, t.id);
|
|
307
307
|
})
|
|
308
308
|
}),
|
|
309
|
-
j.length > 0 && /* @__PURE__ */
|
|
309
|
+
j.length > 0 && /* @__PURE__ */ g("p", {
|
|
310
310
|
className: "mt-1.5 text-xs text-text-secondary",
|
|
311
311
|
children: [
|
|
312
312
|
j.length,
|
|
@@ -317,15 +317,15 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
317
317
|
})
|
|
318
318
|
] })
|
|
319
319
|
] }),
|
|
320
|
-
/* @__PURE__ */
|
|
321
|
-
/* @__PURE__ */
|
|
320
|
+
/* @__PURE__ */ g("div", { children: [
|
|
321
|
+
/* @__PURE__ */ g("label", {
|
|
322
322
|
className: "mb-2 block text-sm font-medium",
|
|
323
|
-
children: [X("devportal.expiration", "Expiration"), /* @__PURE__ */
|
|
323
|
+
children: [X("devportal.expiration", "Expiration"), /* @__PURE__ */ h("span", {
|
|
324
324
|
className: "ml-1.5 text-xs font-normal text-text-secondary",
|
|
325
325
|
children: X("devportal.optional", "(optional)")
|
|
326
326
|
})]
|
|
327
327
|
}),
|
|
328
|
-
/* @__PURE__ */
|
|
328
|
+
/* @__PURE__ */ h("input", {
|
|
329
329
|
type: "datetime-local",
|
|
330
330
|
value: L,
|
|
331
331
|
onChange: (e) => R(e.target.value),
|
|
@@ -333,34 +333,34 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
|
|
|
333
333
|
min: (/* @__PURE__ */ new Date()).toISOString().slice(0, 16),
|
|
334
334
|
className: "block w-full rounded-md border px-3 py-2 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50"
|
|
335
335
|
}),
|
|
336
|
-
/* @__PURE__ */
|
|
336
|
+
/* @__PURE__ */ h("p", {
|
|
337
337
|
className: "mt-1.5 text-xs text-text-secondary",
|
|
338
338
|
children: X("devportal.leaveEmptyForNonExpiring", "Leave empty for a delegation that does not expire.")
|
|
339
339
|
})
|
|
340
340
|
] }),
|
|
341
|
-
z && /* @__PURE__ */
|
|
341
|
+
z && /* @__PURE__ */ g("div", {
|
|
342
342
|
className: "flex items-start gap-3 rounded-md border border-border-subtle bg-status-error-bg-subtle p-4",
|
|
343
|
-
children: [/* @__PURE__ */
|
|
343
|
+
children: [/* @__PURE__ */ h(c, { className: "h-5 w-5 shrink-0 text-status-error-text" }), /* @__PURE__ */ h("p", {
|
|
344
344
|
className: "text-sm text-status-error-text",
|
|
345
345
|
children: z
|
|
346
346
|
})]
|
|
347
347
|
})
|
|
348
348
|
] })]
|
|
349
349
|
}),
|
|
350
|
-
!V && /* @__PURE__ */
|
|
350
|
+
!V && /* @__PURE__ */ g("div", {
|
|
351
351
|
className: "flex justify-end gap-3 border-t p-6",
|
|
352
|
-
children: [/* @__PURE__ */
|
|
352
|
+
children: [/* @__PURE__ */ h("button", {
|
|
353
353
|
type: "button",
|
|
354
354
|
onClick: Q,
|
|
355
355
|
disabled: J,
|
|
356
356
|
className: "rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50",
|
|
357
357
|
children: X("devportal.cancel", "Cancel")
|
|
358
|
-
}), /* @__PURE__ */
|
|
358
|
+
}), /* @__PURE__ */ h("button", {
|
|
359
359
|
type: "button",
|
|
360
360
|
onClick: ce,
|
|
361
361
|
disabled: J || k.length === 0,
|
|
362
362
|
className: e("flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed", "bg-status-success-bg hover:bg-status-success-bg/90"),
|
|
363
|
-
children: J ? /* @__PURE__ */ m
|
|
363
|
+
children: J ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(d, { className: "h-4 w-4 animate-spin" }), X("devportal.granting", "Granting...")] }) : X("devportal.grant", "Grant")
|
|
364
364
|
})]
|
|
365
365
|
})
|
|
366
366
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrantDelegationModal.js","names":[],"sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useEffect,\n useId,\n useRef,\n type FC,\n} from \"react\";\nimport {\n Shield,\n X,\n Loader2,\n CheckCircle,\n AlertCircle,\n Search,\n Check,\n} from \"lucide-react\";\nimport type { ApolloClient } from \"@apollo/client/core\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { cn } from \"../utils/cn\";\nimport { useGrantDelegationMutation } from \"../../generated/global-operations\";\nimport { useMyWorkspacesQuery } from \"../../generated/wspace-operations\";\n\ninterface WorkspaceOption {\n id: string;\n name: string;\n}\n\n// ============================================================================\n// Scope grouping helper\n// ============================================================================\n\nfunction groupScopes(scopes: string[]): Map<string, string[]> {\n const groups = new Map<string, string[]>();\n for (const scope of scopes) {\n const [resource] = scope.split(\":\");\n const group = resource || \"other\";\n if (!groups.has(group)) groups.set(group, []);\n groups.get(group)!.push(scope);\n }\n return groups;\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface GrantDelegationModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n appId: string;\n appName: string;\n appScopes: string[];\n /** Pre-selected workspace IDs (e.g., from installation flow) */\n preSelectedWorkspaceIds?: string[];\n /** Pre-selected scopes (e.g., required scopes from app) */\n preSelectedScopes?: string[];\n onSuccess?: (delegation: unknown) => void;\n}\n\n/**\n * Reusable modal for granting delegation access to an app.\n *\n * Allows users to select scopes, specify workspace IDs,\n * and optionally set an expiration date before granting\n * an app permission to act on their behalf.\n */\nexport const GrantDelegationModal: FC<GrantDelegationModalProps> = ({\n open,\n onOpenChange,\n appId,\n appName,\n appScopes,\n preSelectedWorkspaceIds,\n preSelectedScopes,\n onSuccess,\n}) => {\n const titleId = useId();\n const descriptionId = useId();\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const [selectedScopes, setSelectedScopes] = useState<string[]>(\n preSelectedScopes ?? [],\n );\n const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>(\n preSelectedWorkspaceIds ?? [],\n );\n const [allWorkspaces, setAllWorkspaces] = useState(false);\n const [workspaceSearch, setWorkspaceSearch] = useState(\"\");\n const [expiresAt, setExpiresAt] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // useGatewayClient throws when no MultiGatewayProvider is mounted (e.g. in\n // standalone tests). We intentionally swallow that to render in degraded\n // mode without the workspaces dropdown.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const workspaceClient = (() => {\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useGatewayClient(\"workspace\");\n } catch {\n return null;\n }\n })();\n const { data: workspacesData, loading: loadingWorkspaces } =\n useMyWorkspacesQuery({\n client: (workspaceClient as unknown as ApolloClient | null) ?? undefined,\n skip: !open || !workspaceClient,\n variables: {\n pagination: {\n limit: 100,\n offset: 0,\n },\n },\n fetchPolicy: \"cache-and-network\",\n });\n const workspaces: WorkspaceOption[] =\n workspacesData?.myWorkspaces?.items?.map(\n (workspace: { id: string; name: string }) => ({\n id: workspace.id,\n name: workspace.name,\n }),\n ) ?? [];\n const [grantDelegationMutation, { loading }] = useGrantDelegationMutation({\n refetchQueries: [\"CurrentUserDelegations\"],\n awaitRefetchQueries: true,\n });\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Apply pre-selections when they change\n useEffect(() => {\n if (preSelectedWorkspaceIds?.length)\n setSelectedWorkspaceIds(preSelectedWorkspaceIds);\n }, [preSelectedWorkspaceIds]);\n useEffect(() => {\n if (preSelectedScopes?.length) setSelectedScopes(preSelectedScopes);\n }, [preSelectedScopes]);\n\n const resetState = useCallback(() => {\n setSelectedScopes(preSelectedScopes ?? []);\n setSelectedWorkspaceIds(preSelectedWorkspaceIds ?? []);\n setAllWorkspaces(false);\n setWorkspaceSearch(\"\");\n setExpiresAt(\"\");\n setError(null);\n setSuccessMessage(null);\n }, [preSelectedScopes, preSelectedWorkspaceIds]);\n\n const handleClose = useCallback(() => {\n if (loading) return;\n resetState();\n onOpenChange(false);\n }, [loading, onOpenChange, resetState]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n window.requestAnimationFrame(() => {\n dialogRef.current?.focus();\n });\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && !loading) {\n event.preventDefault();\n handleClose();\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.removeEventListener(\"keydown\", onKeyDown);\n previousFocusRef.current?.focus();\n };\n }, [handleClose, loading, open]);\n\n const toggleScope = useCallback((scope: string) => {\n setSelectedScopes((prev) =>\n prev.includes(scope) ? prev.filter((s) => s !== scope) : [...prev, scope],\n );\n }, []);\n\n const toggleWorkspace = useCallback((wsId: string) => {\n setSelectedWorkspaceIds((prev) =>\n prev.includes(wsId) ? prev.filter((id) => id !== wsId) : [...prev, wsId],\n );\n setAllWorkspaces(false);\n }, []);\n\n const handleAllWorkspacesToggle = useCallback(() => {\n setAllWorkspaces((prev) => {\n if (!prev) setSelectedWorkspaceIds([]);\n return !prev;\n });\n }, []);\n\n const filteredWorkspaces = workspaces.filter((ws) =>\n ws.name.toLowerCase().includes(workspaceSearch.toLowerCase()),\n );\n\n const scopeGroups = groupScopes(appScopes);\n\n const handleGrant = useCallback(async () => {\n setError(null);\n\n if (selectedScopes.length === 0) {\n setError(\n tr(\n \"devportal.selectAtLeastOneScope\",\n \"Please select at least one scope to grant.\",\n ),\n );\n return;\n }\n\n let parsedExpiresAt: string | undefined;\n if (expiresAt) {\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) {\n setError(\n tr(\n \"devportal.invalidExpirationDate\",\n \"Invalid expiration date. Please use a valid date format.\",\n ),\n );\n return;\n }\n if (date <= new Date()) {\n setError(\n tr(\n \"devportal.expirationDateFuture\",\n \"Expiration date must be in the future.\",\n ),\n );\n return;\n }\n parsedExpiresAt = date.toISOString();\n }\n\n try {\n const result = await grantDelegationMutation({\n variables: {\n input: {\n appId,\n scopes: selectedScopes,\n workspaceIds:\n !allWorkspaces && selectedWorkspaceIds.length > 0\n ? selectedWorkspaceIds\n : undefined,\n expiresAt: parsedExpiresAt,\n },\n },\n });\n\n if (result.error) {\n setError(\n result.error.message ??\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const delegation = result.data?.grantDelegation;\n if (!delegation) {\n setError(\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const wsCount = allWorkspaces\n ? \"all workspaces\"\n : selectedWorkspaceIds.length > 0\n ? `${selectedWorkspaceIds.length} workspace${selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"}`\n : \"all workspaces\";\n setSuccessMessage(\n `Delegation granted to \"${appName}\" with ${selectedScopes.length} scope${selectedScopes.length !== 1 ? \"s\" : \"\"} for ${wsCount}.`,\n );\n onSuccess?.(delegation);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n }\n }, [\n selectedScopes,\n selectedWorkspaceIds,\n allWorkspaces,\n expiresAt,\n appId,\n appName,\n grantDelegationMutation,\n onSuccess,\n ]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <button\n type=\"button\"\n aria-label=\"Close delegation dialog backdrop\"\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={handleClose}\n />\n\n {/* Dialog */}\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={descriptionId}\n tabIndex={-1}\n className=\"relative z-10 w-full max-w-lg rounded-lg bg-bg-surface shadow-xl\"\n >\n {/* Header */}\n <div className=\"flex items-start gap-4 border-b p-6\">\n <div className=\"rounded-full bg-status-success-bg-subtle p-2\">\n <Shield className=\"h-6 w-6 text-status-success-text\" />\n </div>\n <div className=\"flex-1\">\n <h3 id={titleId} className=\"text-lg font-semibold\">\n Grant Delegation\n </h3>\n <p id={descriptionId} className=\"mt-1 text-sm text-text-secondary\">\n Allow{\" \"}\n <span className=\"font-medium text-text-primary\">{appName}</span>{\" \"}\n to act on your behalf\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"space-y-5 p-6\">\n {/* Success state */}\n {successMessage && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-success-bg-subtle p-4\">\n <CheckCircle className=\"h-5 w-5 shrink-0 text-status-success-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"mt-2 text-sm font-medium text-status-success-text hover:underline\"\n >\n {tr(\"devportal.close\", \"Close\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Form fields (hidden after success) */}\n {!successMessage && (\n <>\n {/* Scope selection (grouped by resource) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.permissionsScopes\", \"Permissions (Scopes)\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {selectedScopes.length} {tr(\"devportal.of\", \"of\")}{\" \"}\n {appScopes.length} {tr(\"devportal.selected\", \"selected\")}\n </span>\n </label>\n {appScopes.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This app has no scopes available.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {/* Quick actions */}\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([...appScopes])}\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.selectAll\", \"Select all\")}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setSelectedScopes(\n appScopes.filter(\n (s) => s.includes(\":read\") || s.includes(\":list\"),\n ),\n )\n }\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.readOnly\", \"Read-only\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([])}\n className=\"text-text-secondary hover:underline\"\n >\n {tr(\"devportal.clear\", \"Clear\")}\n </button>\n </div>\n {/* Grouped scopes */}\n {[...scopeGroups.entries()].map(([group, scopes]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-semibold uppercase text-text-secondary\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {scopes.map((scope) => {\n const isSelected = selectedScopes.includes(scope);\n const action = scope.split(\":\")[1] || scope;\n const isWrite =\n action.includes(\"write\") ||\n action.includes(\"create\") ||\n action.includes(\"delete\") ||\n action.includes(\"admin\");\n return (\n <button\n key={scope}\n type=\"button\"\n onClick={() => toggleScope(scope)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n isSelected\n ? isWrite\n ? \"bg-status-warning-bg text-white\"\n : \"bg-status-success-bg text-white\"\n : \"border bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80\",\n )}\n >\n {action}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Workspace Selection (multi-select picker) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.workspaceAccess\", \"Workspace Access\")}\n </label>\n\n {/* All workspaces toggle */}\n <button\n type=\"button\"\n onClick={handleAllWorkspacesToggle}\n disabled={loading}\n className={cn(\n \"mb-2 flex w-full items-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors\",\n allWorkspaces\n ? \"border-status-success-bg bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n {allWorkspaces && (\n <Check className=\"h-4 w-4 text-status-success-text\" />\n )}\n <span>\n {tr(\n \"devportal.allWorkspacesUnrestricted\",\n \"All workspaces (unrestricted)\",\n )}\n </span>\n </button>\n\n {!allWorkspaces && (\n <>\n {/* Search */}\n <div className=\"relative mb-2\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n value={workspaceSearch}\n onChange={(e) => setWorkspaceSearch(e.target.value)}\n placeholder={tr(\n \"devportal.searchWorkspaces\",\n \"Search workspaces...\",\n )}\n disabled={loading}\n className=\"block w-full rounded-md border py-2 pl-9 pr-3 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n </div>\n\n {/* Workspace list */}\n <div className=\"max-h-40 overflow-y-auto rounded-md border\">\n {loadingWorkspaces ? (\n <div className=\"flex items-center justify-center p-4\">\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-sm text-text-secondary\">\n {tr(\n \"devportal.loadingWorkspaces\",\n \"Loading workspaces...\",\n )}\n </span>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <p className=\"p-4 text-center text-sm text-text-secondary\">\n {workspaces.length === 0\n ? tr(\n \"devportal.noWorkspacesFound\",\n \"No workspaces found\",\n )\n : tr(\n \"devportal.noMatchingWorkspaces\",\n \"No matching workspaces\",\n )}\n </p>\n ) : (\n filteredWorkspaces.map((ws) => {\n const isSelected = selectedWorkspaceIds.includes(\n ws.id,\n );\n return (\n <button\n key={ws.id}\n type=\"button\"\n onClick={() => toggleWorkspace(ws.id)}\n disabled={loading}\n className={cn(\n \"flex w-full items-center gap-2 border-b px-3 py-2 text-left text-sm transition-colors last:border-b-0\",\n isSelected\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n <div\n className={cn(\n \"flex h-4 w-4 shrink-0 items-center justify-center rounded border\",\n isSelected\n ? \"border-status-success-bg bg-status-success-bg\"\n : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && (\n <Check className=\"h-3 w-3 text-white\" />\n )}\n </div>\n <span className=\"font-medium\">{ws.name}</span>\n </button>\n );\n })\n )}\n </div>\n\n {/* Selected count */}\n {selectedWorkspaceIds.length > 0 && (\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {selectedWorkspaceIds.length} workspace\n {selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"} selected\n </p>\n )}\n </>\n )}\n </div>\n\n {/* Expiration */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.expiration\", \"Expiration\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {tr(\"devportal.optional\", \"(optional)\")}\n </span>\n </label>\n <input\n type=\"datetime-local\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n disabled={loading}\n min={new Date().toISOString().slice(0, 16)}\n className=\"block w-full rounded-md border px-3 py-2 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {tr(\n \"devportal.leaveEmptyForNonExpiring\",\n \"Leave empty for a delegation that does not expire.\",\n )}\n </p>\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-error-bg-subtle p-4\">\n <AlertCircle className=\"h-5 w-5 shrink-0 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n </>\n )}\n </div>\n\n {/* Footer */}\n {!successMessage && (\n <div className=\"flex justify-end gap-3 border-t p-6\">\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleGrant}\n disabled={loading || selectedScopes.length === 0}\n className={cn(\n \"flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed\",\n \"bg-status-success-bg hover:bg-status-success-bg/90\",\n )}\n >\n {loading ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr(\"devportal.granting\", \"Granting...\")}\n </>\n ) : (\n tr(\"devportal.grant\", \"Grant\")\n )}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrantDelegationModal.js","names":[],"sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useEffect,\n useId,\n useRef,\n type FC,\n} from \"react\";\nimport {\n Shield,\n X,\n Loader2,\n CheckCircle,\n AlertCircle,\n Search,\n Check,\n} from \"lucide-react\";\nimport type { ApolloClient } from \"@apollo/client/core\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { cn } from \"../utils/cn\";\nimport { useGrantDelegationMutation } from \"../../generated/global-operations\";\nimport { useMyWorkspacesQuery } from \"../../generated/wspace-operations\";\n\ninterface WorkspaceOption {\n id: string;\n name: string;\n}\n\n// ============================================================================\n// Scope grouping helper\n// ============================================================================\n\nfunction groupScopes(scopes: string[]): Map<string, string[]> {\n const groups = new Map<string, string[]>();\n for (const scope of scopes) {\n const [resource] = scope.split(\":\");\n const group = resource || \"other\";\n if (!groups.has(group)) groups.set(group, []);\n groups.get(group)!.push(scope);\n }\n return groups;\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface GrantDelegationModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n appId: string;\n appName: string;\n appScopes: string[];\n /** Pre-selected workspace IDs (e.g., from installation flow) */\n preSelectedWorkspaceIds?: string[];\n /** Pre-selected scopes (e.g., required scopes from app) */\n preSelectedScopes?: string[];\n onSuccess?: (delegation: unknown) => void;\n}\n\n/**\n * Reusable modal for granting delegation access to an app.\n *\n * Allows users to select scopes, specify workspace IDs,\n * and optionally set an expiration date before granting\n * an app permission to act on their behalf.\n */\nexport const GrantDelegationModal: FC<GrantDelegationModalProps> = ({\n open,\n onOpenChange,\n appId,\n appName,\n appScopes,\n preSelectedWorkspaceIds,\n preSelectedScopes,\n onSuccess,\n}) => {\n const titleId = useId();\n const descriptionId = useId();\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const [selectedScopes, setSelectedScopes] = useState<string[]>(\n preSelectedScopes ?? [],\n );\n const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>(\n preSelectedWorkspaceIds ?? [],\n );\n const [allWorkspaces, setAllWorkspaces] = useState(false);\n const [workspaceSearch, setWorkspaceSearch] = useState(\"\");\n const [expiresAt, setExpiresAt] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // useGatewayClient throws when no MultiGatewayProvider is mounted (e.g. in\n // standalone tests). We intentionally swallow that to render in degraded\n // mode without the workspaces dropdown.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const workspaceClient = (() => {\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useGatewayClient(\"workspace\");\n } catch {\n return null;\n }\n })();\n const { data: workspacesData, loading: loadingWorkspaces } =\n useMyWorkspacesQuery({\n client: (workspaceClient as unknown as ApolloClient | null) ?? undefined,\n skip: !open || !workspaceClient,\n variables: {\n pagination: {\n limit: 100,\n offset: 0,\n },\n },\n fetchPolicy: \"cache-and-network\",\n });\n const workspaces: WorkspaceOption[] =\n workspacesData?.myWorkspaces?.items?.map(\n (workspace: { id: string; name: string }) => ({\n id: workspace.id,\n name: workspace.name,\n }),\n ) ?? [];\n const [grantDelegationMutation, { loading }] = useGrantDelegationMutation({\n refetchQueries: [\"CurrentUserDelegations\"],\n awaitRefetchQueries: true,\n });\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Apply pre-selections when they change\n useEffect(() => {\n if (preSelectedWorkspaceIds?.length)\n setSelectedWorkspaceIds(preSelectedWorkspaceIds);\n }, [preSelectedWorkspaceIds]);\n useEffect(() => {\n if (preSelectedScopes?.length) setSelectedScopes(preSelectedScopes);\n }, [preSelectedScopes]);\n\n const resetState = useCallback(() => {\n setSelectedScopes(preSelectedScopes ?? []);\n setSelectedWorkspaceIds(preSelectedWorkspaceIds ?? []);\n setAllWorkspaces(false);\n setWorkspaceSearch(\"\");\n setExpiresAt(\"\");\n setError(null);\n setSuccessMessage(null);\n }, [preSelectedScopes, preSelectedWorkspaceIds]);\n\n const handleClose = useCallback(() => {\n if (loading) return;\n resetState();\n onOpenChange(false);\n }, [loading, onOpenChange, resetState]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n window.requestAnimationFrame(() => {\n dialogRef.current?.focus();\n });\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && !loading) {\n event.preventDefault();\n handleClose();\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.removeEventListener(\"keydown\", onKeyDown);\n previousFocusRef.current?.focus();\n };\n }, [handleClose, loading, open]);\n\n const toggleScope = useCallback((scope: string) => {\n setSelectedScopes((prev) =>\n prev.includes(scope) ? prev.filter((s) => s !== scope) : [...prev, scope],\n );\n }, []);\n\n const toggleWorkspace = useCallback((wsId: string) => {\n setSelectedWorkspaceIds((prev) =>\n prev.includes(wsId) ? prev.filter((id) => id !== wsId) : [...prev, wsId],\n );\n setAllWorkspaces(false);\n }, []);\n\n const handleAllWorkspacesToggle = useCallback(() => {\n setAllWorkspaces((prev) => {\n if (!prev) setSelectedWorkspaceIds([]);\n return !prev;\n });\n }, []);\n\n const filteredWorkspaces = workspaces.filter((ws) =>\n ws.name.toLowerCase().includes(workspaceSearch.toLowerCase()),\n );\n\n const scopeGroups = groupScopes(appScopes);\n\n const handleGrant = useCallback(async () => {\n setError(null);\n\n if (selectedScopes.length === 0) {\n setError(\n tr(\n \"devportal.selectAtLeastOneScope\",\n \"Please select at least one scope to grant.\",\n ),\n );\n return;\n }\n\n let parsedExpiresAt: string | undefined;\n if (expiresAt) {\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) {\n setError(\n tr(\n \"devportal.invalidExpirationDate\",\n \"Invalid expiration date. Please use a valid date format.\",\n ),\n );\n return;\n }\n if (date <= new Date()) {\n setError(\n tr(\n \"devportal.expirationDateFuture\",\n \"Expiration date must be in the future.\",\n ),\n );\n return;\n }\n parsedExpiresAt = date.toISOString();\n }\n\n try {\n const result = await grantDelegationMutation({\n variables: {\n input: {\n appId,\n scopes: selectedScopes,\n workspaceIds:\n !allWorkspaces && selectedWorkspaceIds.length > 0\n ? selectedWorkspaceIds\n : undefined,\n expiresAt: parsedExpiresAt,\n },\n },\n });\n\n if (result.error) {\n setError(\n result.error.message ??\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const delegation = result.data?.grantDelegation;\n if (!delegation) {\n setError(\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const wsCount = allWorkspaces\n ? \"all workspaces\"\n : selectedWorkspaceIds.length > 0\n ? `${selectedWorkspaceIds.length} workspace${selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"}`\n : \"all workspaces\";\n setSuccessMessage(\n `Delegation granted to \"${appName}\" with ${selectedScopes.length} scope${selectedScopes.length !== 1 ? \"s\" : \"\"} for ${wsCount}.`,\n );\n onSuccess?.(delegation);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n }\n }, [\n selectedScopes,\n selectedWorkspaceIds,\n allWorkspaces,\n expiresAt,\n appId,\n appName,\n grantDelegationMutation,\n onSuccess,\n ]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <button\n type=\"button\"\n aria-label=\"Close delegation dialog backdrop\"\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={handleClose}\n />\n\n {/* Dialog */}\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={descriptionId}\n tabIndex={-1}\n className=\"relative z-10 w-full max-w-lg rounded-lg bg-bg-surface shadow-xl\"\n >\n {/* Header */}\n <div className=\"flex items-start gap-4 border-b p-6\">\n <div className=\"rounded-full bg-status-success-bg-subtle p-2\">\n <Shield className=\"h-6 w-6 text-status-success-text\" />\n </div>\n <div className=\"flex-1\">\n <h3 id={titleId} className=\"text-lg font-semibold\">\n Grant Delegation\n </h3>\n <p id={descriptionId} className=\"mt-1 text-sm text-text-secondary\">\n Allow{\" \"}\n <span className=\"font-medium text-text-primary\">{appName}</span>{\" \"}\n to act on your behalf\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"space-y-5 p-6\">\n {/* Success state */}\n {successMessage && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-success-bg-subtle p-4\">\n <CheckCircle className=\"h-5 w-5 shrink-0 text-status-success-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"mt-2 text-sm font-medium text-status-success-text hover:underline\"\n >\n {tr(\"devportal.close\", \"Close\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Form fields (hidden after success) */}\n {!successMessage && (\n <>\n {/* Scope selection (grouped by resource) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.permissionsScopes\", \"Permissions (Scopes)\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {selectedScopes.length} {tr(\"devportal.of\", \"of\")}{\" \"}\n {appScopes.length} {tr(\"devportal.selected\", \"selected\")}\n </span>\n </label>\n {appScopes.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This app has no scopes available.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {/* Quick actions */}\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([...appScopes])}\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.selectAll\", \"Select all\")}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setSelectedScopes(\n appScopes.filter(\n (s) => s.includes(\":read\") || s.includes(\":list\"),\n ),\n )\n }\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.readOnly\", \"Read-only\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([])}\n className=\"text-text-secondary hover:underline\"\n >\n {tr(\"devportal.clear\", \"Clear\")}\n </button>\n </div>\n {/* Grouped scopes */}\n {[...scopeGroups.entries()].map(([group, scopes]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-semibold uppercase text-text-secondary\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {scopes.map((scope) => {\n const isSelected = selectedScopes.includes(scope);\n const action = scope.split(\":\")[1] || scope;\n const isWrite =\n action.includes(\"write\") ||\n action.includes(\"create\") ||\n action.includes(\"delete\") ||\n action.includes(\"admin\");\n return (\n <button\n key={scope}\n type=\"button\"\n onClick={() => toggleScope(scope)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n isSelected\n ? isWrite\n ? \"bg-status-warning-bg text-white\"\n : \"bg-status-success-bg text-white\"\n : \"border bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80\",\n )}\n >\n {action}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Workspace Selection (multi-select picker) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.workspaceAccess\", \"Workspace Access\")}\n </label>\n\n {/* All workspaces toggle */}\n <button\n type=\"button\"\n onClick={handleAllWorkspacesToggle}\n disabled={loading}\n className={cn(\n \"mb-2 flex w-full items-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors\",\n allWorkspaces\n ? \"border-status-success-bg bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n {allWorkspaces && (\n <Check className=\"h-4 w-4 text-status-success-text\" />\n )}\n <span>\n {tr(\n \"devportal.allWorkspacesUnrestricted\",\n \"All workspaces (unrestricted)\",\n )}\n </span>\n </button>\n\n {!allWorkspaces && (\n <>\n {/* Search */}\n <div className=\"relative mb-2\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n value={workspaceSearch}\n onChange={(e) => setWorkspaceSearch(e.target.value)}\n placeholder={tr(\n \"devportal.searchWorkspaces\",\n \"Search workspaces...\",\n )}\n disabled={loading}\n className=\"block w-full rounded-md border py-2 pl-9 pr-3 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n </div>\n\n {/* Workspace list */}\n <div className=\"max-h-40 overflow-y-auto rounded-md border\">\n {loadingWorkspaces ? (\n <div className=\"flex items-center justify-center p-4\">\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-sm text-text-secondary\">\n {tr(\n \"devportal.loadingWorkspaces\",\n \"Loading workspaces...\",\n )}\n </span>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <p className=\"p-4 text-center text-sm text-text-secondary\">\n {workspaces.length === 0\n ? tr(\n \"devportal.noWorkspacesFound\",\n \"No workspaces found\",\n )\n : tr(\n \"devportal.noMatchingWorkspaces\",\n \"No matching workspaces\",\n )}\n </p>\n ) : (\n filteredWorkspaces.map((ws) => {\n const isSelected = selectedWorkspaceIds.includes(\n ws.id,\n );\n return (\n <button\n key={ws.id}\n type=\"button\"\n onClick={() => toggleWorkspace(ws.id)}\n disabled={loading}\n className={cn(\n \"flex w-full items-center gap-2 border-b px-3 py-2 text-left text-sm transition-colors last:border-b-0\",\n isSelected\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n <div\n className={cn(\n \"flex h-4 w-4 shrink-0 items-center justify-center rounded border\",\n isSelected\n ? \"border-status-success-bg bg-status-success-bg\"\n : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && (\n <Check className=\"h-3 w-3 text-white\" />\n )}\n </div>\n <span className=\"font-medium\">{ws.name}</span>\n </button>\n );\n })\n )}\n </div>\n\n {/* Selected count */}\n {selectedWorkspaceIds.length > 0 && (\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {selectedWorkspaceIds.length} workspace\n {selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"} selected\n </p>\n )}\n </>\n )}\n </div>\n\n {/* Expiration */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.expiration\", \"Expiration\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {tr(\"devportal.optional\", \"(optional)\")}\n </span>\n </label>\n <input\n type=\"datetime-local\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n disabled={loading}\n min={new Date().toISOString().slice(0, 16)}\n className=\"block w-full rounded-md border px-3 py-2 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {tr(\n \"devportal.leaveEmptyForNonExpiring\",\n \"Leave empty for a delegation that does not expire.\",\n )}\n </p>\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-error-bg-subtle p-4\">\n <AlertCircle className=\"h-5 w-5 shrink-0 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n </>\n )}\n </div>\n\n {/* Footer */}\n {!successMessage && (\n <div className=\"flex justify-end gap-3 border-t p-6\">\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleGrant}\n disabled={loading || selectedScopes.length === 0}\n className={cn(\n \"flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed\",\n \"bg-status-success-bg hover:bg-status-success-bg/90\",\n )}\n >\n {loading ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr(\"devportal.granting\", \"Granting...\")}\n </>\n ) : (\n tr(\"devportal.grant\", \"Grant\")\n )}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingState.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/LoadingState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"LoadingState.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/LoadingState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CA0B9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAIrD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAWnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAQrD,CAAC"}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useTr as e } from "../utils/i18n.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
3
|
import { Loader2 as n } from "lucide-react";
|
|
4
4
|
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
5
|
//#region src/devportal/components/LoadingState.tsx
|
|
6
6
|
var a = ({ message: a, className: o, size: s = "md" }) => {
|
|
7
|
-
let
|
|
8
|
-
let n = c(e);
|
|
9
|
-
return n === e ? t : n;
|
|
10
|
-
})("devportal.loadingDots", "Loading...");
|
|
7
|
+
let c = e(), l = a ?? c("devportal.loadingDots", "Loading...");
|
|
11
8
|
return /* @__PURE__ */ i("div", {
|
|
12
|
-
className:
|
|
13
|
-
children: [/* @__PURE__ */ r(n, { className:
|
|
9
|
+
className: t("flex flex-col items-center justify-center gap-4 p-8", o),
|
|
10
|
+
children: [/* @__PURE__ */ r(n, { className: t("animate-spin text-primary", {
|
|
14
11
|
sm: "h-4 w-4",
|
|
15
12
|
md: "h-8 w-8",
|
|
16
13
|
lg: "h-12 w-12"
|