From b1c5fa4c46d5d8e789e04179c32219a697a033f5 Mon Sep 17 00:00:00 2001
From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
Date: Fri, 6 Mar 2026 10:13:47 +0100
Subject: [PATCH] refactor(ui): migrate provider wizard forms from HeroUI to
shadcn (#10259)
---
.../organizations/org-setup-form.tsx | 392 +++++++++---------
.../workflow/credentials-role-helper.tsx | 4 +-
.../workflow/forms/base-credentials-form.tsx | 4 +-
.../workflow/forms/connect-account-form.tsx | 6 +-
.../providers/workflow/forms/fields/index.ts | 3 +
.../forms/fields/wizard-input-field.tsx | 172 ++++++++
.../forms/fields/wizard-radio-card.tsx | 35 ++
.../forms/fields/wizard-textarea-field.tsx | 96 +++++
.../forms/provider-connection-info.tsx | 57 +++
.../alibabacloud-role-credentials-form.tsx | 14 +-
.../alibabacloud-static-credentials-form.tsx | 6 +-
...alibabacloud-via-credentials-type-form.tsx | 69 ++-
.../aws-role-credentials-form.tsx | 45 +-
.../aws-static-credentials-form.tsx | 8 +-
...io-group-aws-via-credentials-type-form.tsx | 42 +-
.../cloudflare-api-key-credentials-form.tsx | 6 +-
.../cloudflare-api-token-credentials-form.tsx | 4 +-
...p-cloudflare-via-credentials-type-form.tsx | 44 +-
.../gcp-default-credentials-form.tsx | 8 +-
.../gcp-service-account-key-form.tsx | 4 +-
...io-group-gcp-via-credentials-type-form.tsx | 57 +--
.../credentials-type/github-app-form.tsx | 9 +-
.../github-oauth-app-form.tsx | 4 +-
.../github-personal-access-token-form.tsx | 4 +-
...group-github-via-credentials-type-form.tsx | 63 +--
.../m365-certificate-credentials-form.tsx | 21 +-
.../m365-client-secret-credentials-form.tsx | 8 +-
...o-group-m365-via-credentials-type-form.tsx | 45 +-
.../workflow/forms/test-connection-form.tsx | 4 +-
.../azure-credentials-form.tsx | 8 +-
.../via-credentials/iac-credentials-form.tsx | 4 +-
.../via-credentials/k8s-credentials-form.tsx | 4 +-
.../mongodbatlas-credentials-form.tsx | 6 +-
.../openstack-credentials-form.tsx | 9 +-
.../oraclecloud-credentials-form.tsx | 15 +-
.../workflow/skeleton-provider-workflow.tsx | 34 +-
.../shadcn/radio-group/radio-group.tsx | 46 ++
ui/components/shadcn/textarea/textarea.tsx | 50 +++
ui/components/ui/custom/custom-section.tsx | 23 -
ui/components/ui/custom/index.ts | 1 -
ui/dependency-log.json | 8 +
ui/package.json | 1 +
ui/pnpm-lock.yaml | 34 ++
43 files changed, 917 insertions(+), 560 deletions(-)
create mode 100644 ui/components/providers/workflow/forms/fields/index.ts
create mode 100644 ui/components/providers/workflow/forms/fields/wizard-input-field.tsx
create mode 100644 ui/components/providers/workflow/forms/fields/wizard-radio-card.tsx
create mode 100644 ui/components/providers/workflow/forms/fields/wizard-textarea-field.tsx
create mode 100644 ui/components/providers/workflow/forms/provider-connection-info.tsx
create mode 100644 ui/components/shadcn/radio-group/radio-group.tsx
create mode 100644 ui/components/shadcn/textarea/textarea.tsx
delete mode 100644 ui/components/ui/custom/custom-section.tsx
diff --git a/ui/components/providers/organizations/org-setup-form.tsx b/ui/components/providers/organizations/org-setup-form.tsx
index 46571b66be..0a62efedb7 100644
--- a/ui/components/providers/organizations/org-setup-form.tsx
+++ b/ui/components/providers/organizations/org-setup-form.tsx
@@ -12,11 +12,12 @@ import {
WIZARD_FOOTER_ACTION_TYPE,
WizardFooterConfig,
} from "@/components/providers/wizard/steps/footer-controls";
+import { WizardInputField } from "@/components/providers/workflow/forms/fields";
import { Alert, AlertDescription } from "@/components/shadcn/alert";
import { Button } from "@/components/shadcn/button/button";
import { Checkbox } from "@/components/shadcn/checkbox/checkbox";
-import { Input } from "@/components/shadcn/input/input";
import { TreeSpinner } from "@/components/shadcn/tree-view/tree-spinner";
+import { Form } from "@/components/ui/form";
import { getAWSCredentialsTemplateLinks } from "@/lib";
import { ORG_SETUP_PHASE, OrgSetupPhase } from "@/types/organizations";
@@ -68,14 +69,7 @@ export function OrgSetupForm({
const [setupPhase, setSetupPhase] = useState(initialPhase);
const formId = "org-wizard-setup-form";
- const {
- control,
- register,
- handleSubmit,
- formState: { errors, isSubmitting, isValid },
- setError,
- watch,
- } = useForm({
+ const form = useForm({
resolver: zodResolver(orgSetupSchema),
mode: "onChange",
reValidateMode: "onChange",
@@ -86,10 +80,13 @@ export function OrgSetupForm({
stackSetDeployed: false,
},
});
- const awsOrgIdField = register("awsOrgId", {
- setValueAs: (value: unknown) =>
- typeof value === "string" ? value.toLowerCase() : value,
- });
+ const {
+ control,
+ handleSubmit,
+ formState: { errors, isSubmitting, isValid },
+ setError,
+ watch,
+ } = form;
const awsOrgId = watch("awsOrgId") || "";
const isOrgIdValid = /^o-[a-z0-9]{10,32}$/.test(awsOrgId.trim());
@@ -180,228 +177,209 @@ export function OrgSetupForm({
}, [apiError, formId]);
return (
-
>
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx
index 3393fbdff5..ab3bef8a93 100644
--- a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx
+++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx
@@ -2,7 +2,7 @@
import { Control } from "react-hook-form";
-import { CustomInput } from "@/components/ui/custom";
+import { WizardInputField } from "@/components/providers/workflow/forms/fields";
import { M365ClientSecretCredentials } from "@/types";
export const M365ClientSecretCredentialsForm = ({
@@ -20,7 +20,7 @@ export const M365ClientSecretCredentialsForm = ({
Please provide your Microsoft 365 application credentials.
-
-
- (
<>
{
+ onValueChange={(value: string) => {
field.onChange(value);
- if (onChange) {
- onChange(value);
- }
+ onChange?.(value);
}}
>
-
-
- Select Authentication Method
-
-
-
- App Client Secret Credentials
-
-
-
-
- App Certificate Credentials
-
-
-
+
+ Select Authentication Method
+
+
+ App Client Secret Credentials
+
+
+ App Certificate Credentials
+
{errorMessage && (
diff --git a/ui/components/providers/workflow/forms/test-connection-form.tsx b/ui/components/providers/workflow/forms/test-connection-form.tsx
index ccdf0afd32..2ee612701b 100644
--- a/ui/components/providers/workflow/forms/test-connection-form.tsx
+++ b/ui/components/providers/workflow/forms/test-connection-form.tsx
@@ -22,7 +22,7 @@ import { checkTaskStatus } from "@/lib/helper";
import { ProviderType } from "@/types";
import { ApiError, testConnectionFormSchema } from "@/types";
-import { ProviderInfo } from "../..";
+import { ProviderConnectionInfo } from "./provider-connection-info";
type FormValues = z.input;
@@ -243,7 +243,7 @@ export const TestConnectionForm = ({
>
)}
-
-
-
-
-
-
-
-
-
- }
/>
-
-
-
-
- {
return (
-
+
-
-
-
-
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
+
);
};
diff --git a/ui/components/shadcn/radio-group/radio-group.tsx b/ui/components/shadcn/radio-group/radio-group.tsx
new file mode 100644
index 0000000000..5765073031
--- /dev/null
+++ b/ui/components/shadcn/radio-group/radio-group.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
+
+import { cn } from "@/lib/utils";
+
+function RadioGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function RadioGroupItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ );
+}
+
+export { RadioGroup, RadioGroupItem };
diff --git a/ui/components/shadcn/textarea/textarea.tsx b/ui/components/shadcn/textarea/textarea.tsx
new file mode 100644
index 0000000000..096c56262e
--- /dev/null
+++ b/ui/components/shadcn/textarea/textarea.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import { cva, type VariantProps } from "class-variance-authority";
+import { ComponentProps, forwardRef } from "react";
+
+import { cn } from "@/lib/utils";
+
+const textareaVariants = cva(
+ "flex w-full rounded-lg border text-sm transition-all outline-none resize-none disabled:cursor-not-allowed disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-border-input-primary bg-bg-input-primary dark:bg-input/30 hover:bg-bg-neutral-secondary dark:hover:bg-input/50 focus:border-border-input-primary-press focus:ring-1 focus:ring-inset focus:ring-border-input-primary-press placeholder:text-text-neutral-tertiary",
+ ghost:
+ "border-transparent bg-transparent hover:bg-bg-neutral-tertiary focus:bg-bg-neutral-tertiary placeholder:text-text-neutral-tertiary",
+ },
+ textareaSize: {
+ default: "min-h-16 px-4 py-3",
+ sm: "min-h-12 px-3 py-2 text-xs",
+ lg: "min-h-24 px-5 py-4",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ textareaSize: "default",
+ },
+ },
+);
+
+export interface TextareaProps
+ extends Omit, "size">,
+ VariantProps {}
+
+const Textarea = forwardRef(
+ ({ className, variant, textareaSize, ...props }, ref) => {
+ return (
+
+ );
+ },
+);
+
+Textarea.displayName = "Textarea";
+
+export { Textarea, textareaVariants };
diff --git a/ui/components/ui/custom/custom-section.tsx b/ui/components/ui/custom/custom-section.tsx
deleted file mode 100644
index 197bac35c5..0000000000
--- a/ui/components/ui/custom/custom-section.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { type ReactNode } from "react";
-
-interface CustomSectionProps {
- title: string | ReactNode;
- children: ReactNode;
- action?: ReactNode;
-}
-
-export const CustomSection = ({
- title,
- children,
- action,
-}: CustomSectionProps) => (
-
-
-
- {title}
-
- {action &&
{action}
}
-
- {children}
-
-);
diff --git a/ui/components/ui/custom/index.ts b/ui/components/ui/custom/index.ts
index 6314862ea7..8a1bcd488f 100644
--- a/ui/components/ui/custom/index.ts
+++ b/ui/components/ui/custom/index.ts
@@ -3,7 +3,6 @@ export * from "./custom-input";
export * from "./custom-link";
export * from "./custom-modal-buttons";
export * from "./custom-radio";
-export * from "./custom-section";
export * from "./custom-server-input";
export * from "./custom-table-link";
export * from "./custom-textarea";
diff --git a/ui/dependency-log.json b/ui/dependency-log.json
index 79fadb3a75..3e05a4e6f9 100644
--- a/ui/dependency-log.json
+++ b/ui/dependency-log.json
@@ -159,6 +159,14 @@
"strategy": "installed",
"generatedAt": "2025-11-19T12:28:39.510Z"
},
+ {
+ "section": "dependencies",
+ "name": "@radix-ui/react-radio-group",
+ "from": "1.3.8",
+ "to": "1.3.8",
+ "strategy": "installed",
+ "generatedAt": "2026-03-05T14:12:59.626Z"
+ },
{
"section": "dependencies",
"name": "@radix-ui/react-scroll-area",
diff --git a/ui/package.json b/ui/package.json
index 82a23dd057..ea9688e854 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -47,6 +47,7 @@
"@radix-ui/react-icons": "1.3.2",
"@radix-ui/react-label": "2.1.7",
"@radix-ui/react-popover": "1.1.15",
+ "@radix-ui/react-radio-group": "1.3.8",
"@radix-ui/react-scroll-area": "1.2.10",
"@radix-ui/react-select": "2.2.5",
"@radix-ui/react-separator": "1.1.7",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 6639173347..389e626987 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -92,6 +92,9 @@ importers:
'@radix-ui/react-popover':
specifier: 1.1.15
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-radio-group':
+ specifier: 1.3.8
+ version: 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@radix-ui/react-scroll-area':
specifier: 1.2.10
version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -3047,6 +3050,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-radio-group@1.3.8':
+ resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-roving-focus@1.1.10':
resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==}
peerDependencies:
@@ -13078,6 +13094,24 @@ snapshots:
'@types/react': 19.2.8
'@types/react-dom': 19.2.3(@types/react@19.2.8)
+ '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.8)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.8)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.8)(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.8)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.8)(react@19.2.4)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.8)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.8
+ '@types/react-dom': 19.2.3(@types/react@19.2.8)
+
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@radix-ui/primitive': 1.1.2