Initial commit: Plane
Some checks failed
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Admin Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Sync Repositories / sync_changes (push) Has been cancelled

Synced from upstream: 8853637e981ed7d8a6cff32bd98e7afe20f54362
This commit is contained in:
chuan
2025-11-07 00:00:52 +08:00
commit 8ebde8aa05
4886 changed files with 462270 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
import React from "react";
// react hook form
import { useFormContext } from "react-hook-form";
import type { IJiraImporterForm } from "@plane/types";
// types
export const JiraConfirmImport: React.FC = () => {
const { watch } = useFormContext<IJiraImporterForm>();
return (
<div className="h-full w-full overflow-y-auto">
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-2">
<h3 className="text-lg font-semibold">Confirm</h3>
</div>
<div className="col-span-1">
<p className="text-sm text-custom-text-200">Migrating</p>
</div>
<div className="col-span-1 flex items-center justify-between">
<div>
<h4 className="mb-2 text-lg font-semibold">{watch("data.total_issues")}</h4>
<p className="text-sm text-custom-text-200">Work items</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{watch("data.total_states")}</h4>
<p className="text-sm text-custom-text-200">States</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{watch("data.total_modules")}</h4>
<p className="text-sm text-custom-text-200">Modules</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{watch("data.total_labels")}</h4>
<p className="text-sm text-custom-text-200">Labels</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{watch("data.users").filter((user) => user.import).length}</h4>
<p className="text-sm text-custom-text-200">User</p>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,223 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useFormContext, Controller } from "react-hook-form";
import { Plus } from "lucide-react";
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
import type { IJiraImporterForm } from "@plane/types";
// hooks
// components
import { CustomSelect, Input } from "@plane/ui";
// helpers
import { checkEmailValidity } from "@plane/utils";
import { captureClick } from "@/helpers/event-tracker.helper";
import { useCommandPalette } from "@/hooks/store/use-command-palette";
import { useProject } from "@/hooks/store/use-project";
// types
export const JiraGetImportDetail: React.FC = observer(() => {
// store hooks
const { toggleCreateProjectModal } = useCommandPalette();
const { workspaceProjectIds, getProjectById } = useProject();
// form info
const {
control,
formState: { errors },
} = useFormContext<IJiraImporterForm>();
return (
<div className="h-full w-full space-y-8 overflow-y-auto">
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Jira Personal Access Token</h3>
<p className="text-sm text-custom-text-200">
Get to know your access token by navigating to{" "}
<Link href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank" rel="noreferrer">
<span className="text-custom-primary underline">Atlassian Settings</span>
</Link>
</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.api_token"
rules={{
required: "Please enter your personal access token.",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.api_token"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.metadata?.api_token)}
placeholder="XXXXXXXX"
className="w-full"
autoComplete="off"
/>
)}
/>
{errors.metadata?.api_token && <p className="text-xs text-red-500">{errors.metadata.api_token.message}</p>}
</div>
</div>
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Jira Project Key</h3>
<p className="text-sm text-custom-text-200">If XXX-123 is your work item, then enter XXX</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.project_key"
rules={{
required: "Please enter your project key.",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.project_key"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.metadata?.project_key)}
placeholder="LIN"
className="w-full"
/>
)}
/>
{errors.metadata?.project_key && (
<p className="text-xs text-red-500">{errors.metadata.project_key.message}</p>
)}
</div>
</div>
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Jira Email Address</h3>
<p className="text-sm text-custom-text-200">Enter the Email account that you use in Jira account</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.email"
rules={{
required: "Please enter email address.",
validate: (value) => checkEmailValidity(value) || "Please enter a valid email address",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.email"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.metadata?.email)}
placeholder="name@company.com"
className="w-full"
/>
)}
/>
{errors.metadata?.email && <p className="text-xs text-red-500">{errors.metadata.email.message}</p>}
</div>
</div>
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Jira Installation or Cloud Host Name</h3>
<p className="text-sm text-custom-text-200">Enter your companies cloud host name</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.cloud_hostname"
rules={{
required: "Please enter your cloud host name.",
validate: (value) => !/^https?:\/\//.test(value) || "Hostname should not begin with http:// or https://",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.cloud_hostname"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.metadata?.cloud_hostname)}
placeholder="my-company.atlassian.net"
className="w-full"
/>
)}
/>
{errors.metadata?.cloud_hostname && (
<p className="text-xs text-red-500">{errors.metadata.cloud_hostname.message}</p>
)}
</div>
</div>
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Import to project</h3>
<p className="text-sm text-custom-text-200">Select which project you want to import to.</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="project_id"
rules={{ required: "Please select a project." }}
render={({ field: { value, onChange } }) => (
<CustomSelect
value={value}
input
onChange={onChange}
label={
<span>
{value && value.trim() !== "" ? (
getProjectById(value)?.name
) : (
<span className="text-custom-text-200">Select a project</span>
)}
</span>
}
>
{workspaceProjectIds && workspaceProjectIds.length > 0 ? (
workspaceProjectIds.map((projectId) => {
const projectDetails = getProjectById(projectId);
if (!projectDetails) return;
return (
<CustomSelect.Option key={projectId} value={projectId}>
{projectDetails.name}
</CustomSelect.Option>
);
})
) : (
<div className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200">
<p>You don{"'"}t have any project. Please create a project first.</p>
</div>
)}
<div>
<button
type="button"
data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON}
onClick={() => {
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.CREATE_PROJECT_JIRA_IMPORT_DETAIL_PAGE });
toggleCreateProjectModal(true);
}}
className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200"
>
<Plus className="h-4 w-4 text-custom-text-200" />
<span>Create new project</span>
</button>
</div>
</CustomSelect>
)}
/>
</div>
</div>
</div>
);
});

View File

@@ -0,0 +1,154 @@
"use client";
import type { FC } from "react";
import { useParams } from "next/navigation";
import { useFormContext, useFieldArray, Controller } from "react-hook-form";
import useSWR from "swr";
// plane types
import type { IJiraImporterForm } from "@plane/types";
// plane ui
import { Avatar, CustomSelect, CustomSearchSelect, Input, ToggleSwitch } from "@plane/ui";
// constants
import { getFileURL } from "@plane/utils";
import { WORKSPACE_MEMBERS } from "@/constants/fetch-keys";
// helpers
// plane web services
import { WorkspaceService } from "@/plane-web/services";
const workspaceService = new WorkspaceService();
export const JiraImportUsers: FC = () => {
const { workspaceSlug } = useParams();
// form info
const {
control,
watch,
formState: { errors },
} = useFormContext<IJiraImporterForm>();
const { fields } = useFieldArray({
control,
name: "data.users",
});
const { data: members } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug?.toString() ?? "") : null,
workspaceSlug ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug?.toString() ?? "") : null
);
const options = members
?.map((member) => {
if (!member?.member) return;
return {
value: member.member.email,
query: member.member.display_name ?? "",
content: (
<div className="flex items-center gap-2">
<Avatar name={member?.member.display_name} src={getFileURL(member?.member.avatar_url)} />
{member.member.display_name}
</div>
),
};
})
.filter((member) => !!member) as
| {
value: string;
query: string;
content: React.ReactNode;
}[]
| undefined;
return (
<div className="h-full w-full space-y-10 divide-y-2 divide-custom-border-200 overflow-y-auto">
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Users</h3>
<p className="text-sm text-custom-text-200">Update, invite or choose not to invite assignee</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="data.invite_users"
render={({ field: { value, onChange } }) => <ToggleSwitch onChange={onChange} value={value} />}
/>
</div>
</div>
{watch("data.invite_users") && (
<div className="pt-6">
<div className="grid grid-cols-3 gap-3">
<div className="col-span-1 text-sm text-custom-text-200">Name</div>
<div className="col-span-1 text-sm text-custom-text-200">Import as</div>
</div>
<div className="mt-5 space-y-3">
{fields.map((user, index) => (
<div className="grid grid-cols-3 gap-3" key={`${user.email}-${user.username}`}>
<div className="col-span-1">
<p>{user.username}</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name={`data.users.${index}.import`}
render={({ field: { value, onChange } }) => (
<CustomSelect
input
value={value}
onChange={onChange}
label={<span className="capitalize">{Boolean(value) ? value : ("Ignore" as any)}</span>}
>
<CustomSelect.Option value="invite">Invite by email</CustomSelect.Option>
<CustomSelect.Option value="map">Map to existing</CustomSelect.Option>
<CustomSelect.Option value={false}>Do not import</CustomSelect.Option>
</CustomSelect>
)}
/>
</div>
<div className="col-span-1">
{watch(`data.users.${index}.import`) === "invite" && (
<Controller
control={control}
name={`data.users.${index}.email`}
rules={{
required: "This field is required",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id={`data.users.${index}.email`}
name={`data.users.${index}.email`}
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.data?.users?.[index]?.email)}
className="w-full"
/>
)}
/>
)}
{watch(`data.users.${index}.import`) === "map" && (
<Controller
control={control}
name={`data.users.${index}.email`}
render={({ field: { value, onChange } }) => (
<CustomSearchSelect
value={value}
input
label={value !== "" ? value : "Select user from project"}
options={options}
onChange={onChange}
optionsClassName="w-48"
/>
)}
/>
)}
</div>
</div>
))}
</div>
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,39 @@
export * from "./root";
export * from "./give-details";
export * from "./jira-project-detail";
export * from "./import-users";
export * from "./confirm-import";
import type { IJiraImporterForm } from "@plane/types";
export type TJiraIntegrationSteps =
| "import-configure"
| "display-import-data"
| "select-import-data"
| "import-users"
| "import-confirmation";
export interface IJiraIntegrationData {
state: TJiraIntegrationSteps;
}
export const jiraFormDefaultValues: IJiraImporterForm = {
metadata: {
cloud_hostname: "",
api_token: "",
project_key: "",
email: "",
},
config: {
epics_to_modules: false,
},
data: {
users: [],
invite_users: true,
total_issues: 0,
total_labels: 0,
total_modules: 0,
total_states: 0,
},
project_id: "",
};

View File

@@ -0,0 +1,169 @@
"use client";
import React, { useEffect } from "react";
// next
import { useParams } from "next/navigation";
// swr
import { useFormContext, Controller } from "react-hook-form";
import useSWR from "swr";
import type { IJiraImporterForm, IJiraMetadata } from "@plane/types";
// react hook form
// services
import { ToggleSwitch, Spinner } from "@plane/ui";
import { JIRA_IMPORTER_DETAIL } from "@/constants/fetch-keys";
import { JiraImporterService } from "@/services/integrations";
// fetch keys
// components
import type { IJiraIntegrationData, TJiraIntegrationSteps } from ".";
type Props = {
setCurrentStep: React.Dispatch<React.SetStateAction<IJiraIntegrationData>>;
setDisableTopBarAfter: React.Dispatch<React.SetStateAction<TJiraIntegrationSteps | null>>;
};
// services
const jiraImporterService = new JiraImporterService();
export const JiraProjectDetail: React.FC<Props> = (props) => {
const { setCurrentStep, setDisableTopBarAfter } = props;
const {
watch,
setValue,
control,
formState: { errors },
} = useFormContext<IJiraImporterForm>();
const { workspaceSlug } = useParams();
const params: IJiraMetadata = {
api_token: watch("metadata.api_token"),
project_key: watch("metadata.project_key"),
email: watch("metadata.email"),
cloud_hostname: watch("metadata.cloud_hostname"),
};
const { data: projectInfo, error } = useSWR(
workspaceSlug &&
!errors.metadata?.api_token &&
!errors.metadata?.project_key &&
!errors.metadata?.email &&
!errors.metadata?.cloud_hostname
? JIRA_IMPORTER_DETAIL(workspaceSlug.toString(), params)
: null,
workspaceSlug &&
!errors.metadata?.api_token &&
!errors.metadata?.project_key &&
!errors.metadata?.email &&
!errors.metadata?.cloud_hostname
? () => jiraImporterService.getJiraProjectInfo(workspaceSlug.toString(), params)
: null
);
useEffect(() => {
if (!projectInfo) return;
setValue("data.total_issues", projectInfo.issues);
setValue("data.total_labels", projectInfo.labels);
setValue(
"data.users",
projectInfo.users?.map((user) => ({
email: user.emailAddress,
import: false,
username: user.displayName,
}))
);
setValue("data.total_states", projectInfo.states);
setValue("data.total_modules", projectInfo.modules);
}, [projectInfo, setValue]);
useEffect(() => {
if (error) setDisableTopBarAfter("display-import-data");
else setDisableTopBarAfter(null);
}, [error, setDisableTopBarAfter]);
useEffect(() => {
if (!projectInfo && !error) setDisableTopBarAfter("display-import-data");
else if (!error) setDisableTopBarAfter(null);
}, [projectInfo, error, setDisableTopBarAfter]);
if (!projectInfo && !error) {
return (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
</div>
);
}
if (error) {
return (
<div className="flex h-full w-full items-center justify-center">
<p className="text-sm text-custom-text-200">
Something went wrong. Please{" "}
<button
onClick={() => setCurrentStep({ state: "import-configure" })}
type="button"
className="inline text-custom-primary underline"
>
go back
</button>{" "}
and check your Jira project details.
</p>
</div>
);
}
return (
<div className="h-full w-full space-y-10 overflow-y-auto">
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Import Data</h3>
<p className="text-sm text-custom-text-200">Import Completed. We have found:</p>
</div>
<div className="col-span-1 flex items-center justify-between">
<div>
<h4 className="mb-2 text-lg font-semibold">{projectInfo?.issues}</h4>
<p className="text-sm text-custom-text-200">Work items</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{projectInfo?.states}</h4>
<p className="text-sm text-custom-text-200">States</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{projectInfo?.modules}</h4>
<p className="text-sm text-custom-text-200">Modules</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{projectInfo?.labels}</h4>
<p className="text-sm text-custom-text-200">Labels</p>
</div>
<div>
<h4 className="mb-2 text-lg font-semibold">{projectInfo?.users?.length}</h4>
<p className="text-sm text-custom-text-200">Users</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Import Epics</h3>
<p className="text-sm text-custom-text-200">Import epics as modules</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="config.epics_to_modules"
render={({ field: { value, onChange } }) => <ToggleSwitch onChange={onChange} value={value} />}
/>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,203 @@
"use client";
import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { useParams } from "next/navigation";
import { FormProvider, useForm } from "react-hook-form";
import { mutate } from "swr";
// icons
import { ArrowLeft, Check, List, Settings } from "lucide-react";
import { Button } from "@plane/propel/button";
import { MembersPropertyIcon } from "@plane/propel/icons";
// types
import type { IJiraImporterForm } from "@plane/types";
// assets
import JiraLogo from "@/app/assets/services/jira.svg?url";
// fetch keys
import { IMPORTER_SERVICES_LIST } from "@/constants/fetch-keys";
// hooks
import { useAppRouter } from "@/hooks/use-app-router";
// services
import { JiraImporterService } from "@/services/integrations";
// components
import type { TJiraIntegrationSteps, IJiraIntegrationData } from ".";
import { JiraGetImportDetail, JiraProjectDetail, JiraImportUsers, JiraConfirmImport, jiraFormDefaultValues } from ".";
const integrationWorkflowData: Array<{
title: string;
key: TJiraIntegrationSteps;
icon: any;
}> = [
{
title: "Configure",
key: "import-configure",
icon: Settings,
},
{
title: "Import Data",
key: "display-import-data",
icon: List,
},
{
title: "Users",
key: "import-users",
icon: MembersPropertyIcon,
},
{
title: "Confirm",
key: "import-confirmation",
icon: Check,
},
];
// services
const jiraImporterService = new JiraImporterService();
export const JiraImporterRoot: React.FC = () => {
const [currentStep, setCurrentStep] = useState<IJiraIntegrationData>({
state: "import-configure",
});
const [disableTopBarAfter, setDisableTopBarAfter] = useState<TJiraIntegrationSteps | null>(null);
const router = useAppRouter();
const { workspaceSlug } = useParams();
const methods = useForm<IJiraImporterForm>({
defaultValues: jiraFormDefaultValues,
mode: "all",
reValidateMode: "onChange",
});
const isValid = methods.formState.isValid;
const onSubmit = async (data: IJiraImporterForm) => {
if (!workspaceSlug) return;
await jiraImporterService
.createJiraImporter(workspaceSlug.toString(), data)
.then(() => {
mutate(IMPORTER_SERVICES_LIST(workspaceSlug.toString()));
router.push(`/${workspaceSlug}/settings/imports`);
})
.catch((err) => {
console.error(err);
});
};
const activeIntegrationState = () => {
const currentElementIndex = integrationWorkflowData.findIndex((i) => i?.key === currentStep?.state);
return currentElementIndex;
};
return (
<div className="mt-4 flex h-full flex-col space-y-2">
<Link href={`/${workspaceSlug}/settings/imports`}>
<span className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-custom-text-200 hover:text-custom-text-100">
<div>
<ArrowLeft className="h-3 w-3" />
</div>
<div>Cancel import & go back</div>
</span>
</Link>
<div className="flex h-full flex-col space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4">
<div className="flex items-center gap-2">
<div className="h-10 w-10 flex-shrink-0">
<Image src={JiraLogo} alt="jira logo" />
</div>
<div className="flex h-full w-full items-center justify-center">
{integrationWorkflowData.map((integration, index) => (
<React.Fragment key={integration.key}>
<button
type="button"
onClick={() => {
setCurrentStep({ state: integration.key });
}}
disabled={
index > activeIntegrationState() + 1 ||
Boolean(index === activeIntegrationState() + 1 && disableTopBarAfter)
}
className={`flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border border-custom-border-200 ${
index <= activeIntegrationState()
? `border-custom-primary bg-custom-primary ${
index === activeIntegrationState()
? "border-opacity-100 bg-opacity-100"
: "border-opacity-80 bg-opacity-80"
}`
: "border-custom-border-200"
}`}
>
<integration.icon
className={`h-5 w-5 ${index <= activeIntegrationState() ? "text-white" : "text-custom-text-400"}`}
/>
</button>
{index < integrationWorkflowData.length - 1 && (
<div
key={index}
className={`border-b px-7 ${
index <= activeIntegrationState() - 1 ? `border-custom-primary` : `border-custom-border-200`
}`}
>
{" "}
</div>
)}
</React.Fragment>
))}
</div>
</div>
<div className="relative h-full w-full pt-6">
<FormProvider {...methods}>
<form className="flex h-full w-full flex-col">
<div className="h-full w-full overflow-y-auto">
{currentStep.state === "import-configure" && <JiraGetImportDetail />}
{currentStep.state === "display-import-data" && (
<JiraProjectDetail setDisableTopBarAfter={setDisableTopBarAfter} setCurrentStep={setCurrentStep} />
)}
{currentStep?.state === "import-users" && <JiraImportUsers />}
{currentStep?.state === "import-confirmation" && <JiraConfirmImport />}
</div>
<div className="-mx-4 mt-4 flex justify-end gap-4 border-t border-custom-border-200 p-4 pb-0">
{currentStep?.state !== "import-configure" && (
<Button
variant="neutral-primary"
onClick={() => {
const currentElementIndex = integrationWorkflowData.findIndex(
(i) => i?.key === currentStep?.state
);
setCurrentStep({
state: integrationWorkflowData[currentElementIndex - 1]?.key,
});
}}
>
Back
</Button>
)}
<Button
variant="primary"
disabled={disableTopBarAfter === currentStep?.state || !isValid || methods.formState.isSubmitting}
onClick={() => {
const currentElementIndex = integrationWorkflowData.findIndex((i) => i?.key === currentStep?.state);
if (currentElementIndex === integrationWorkflowData.length - 1) {
methods.handleSubmit(onSubmit)();
} else {
setCurrentStep({
state: integrationWorkflowData[currentElementIndex + 1]?.key,
});
}
}}
>
{currentStep?.state === "import-confirmation" ? "Confirm & Import" : "Next"}
</Button>
</div>
</form>
</FormProvider>
</div>
</div>
</div>
);
};