mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 20:25:43 +00:00
feat: finished org-level integration management flow
This commit is contained in:
@@ -45,4 +45,5 @@ export * from "./tags";
|
||||
export * from "./trustedIps";
|
||||
export * from "./users";
|
||||
export * from "./webhooks";
|
||||
export * from "./workflowIntegrations";
|
||||
export * from "./workspace";
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { useDeleteSlackIntegration, useUpdateSlackIntegration } from "./mutation";
|
||||
export { fetchSlackInstallUrl, useGetSlackIntegrationByProject } from "./queries";
|
||||
export { useGetSlackIntegrationByProject } from "./queries";
|
||||
|
||||
@@ -11,16 +11,6 @@ export const slackKeys = {
|
||||
]
|
||||
};
|
||||
|
||||
export const fetchSlackInstallUrl = async (workspaceId?: string) => {
|
||||
const { data } = await apiRequest.get<string>("/api/v1/slack/install", {
|
||||
params: {
|
||||
projectId: workspaceId
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchSlackIntegrationByProject = async (workspaceId?: string) => {
|
||||
const { data } = await apiRequest.get<ProjectSlackIntegration>("/api/v1/slack", {
|
||||
params: {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export { useDeleteSlackIntegration, useUpdateSlackIntegration } from "./mutation";
|
||||
export {
|
||||
fetchSlackInstallUrl,
|
||||
useGetSlackIntegrationById,
|
||||
useGetSlackIntegrations
|
||||
} from "./queries";
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workflowIntegrationKeys } from "./queries";
|
||||
import { TDeleteSlackIntegrationDTO, TUpdateSlackIntegrationDTO } from "./types";
|
||||
|
||||
export const useUpdateSlackIntegration = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TUpdateSlackIntegrationDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/workflow-integrations/slack/${dto.id}`, dto);
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { orgId, id }) => {
|
||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackWorkflowIntegration(id));
|
||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackWorkflowIntegrations(orgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteSlackIntegration = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TDeleteSlackIntegrationDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.delete(`/api/v1/workflow-integrations/slack/${dto.id}`);
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { orgId, id }) => {
|
||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackWorkflowIntegration(id));
|
||||
queryClient.invalidateQueries(workflowIntegrationKeys.getSlackWorkflowIntegrations(orgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { SlackIntegration } from "./types";
|
||||
|
||||
export const workflowIntegrationKeys = {
|
||||
getSlackWorkflowIntegrations: (orgId?: string) => [{ orgId }, "slack-workflow-integrations"],
|
||||
getSlackWorkflowIntegration: (id?: string) => [{ id }, "slack-workflow-integration"]
|
||||
};
|
||||
|
||||
export const fetchSlackInstallUrl = async ({
|
||||
slug,
|
||||
description
|
||||
}: {
|
||||
slug: string;
|
||||
description?: string;
|
||||
}) => {
|
||||
const { data } = await apiRequest.get<string>("/api/v1/workflow-integrations/slack/install", {
|
||||
params: {
|
||||
slug,
|
||||
description
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchSlackIntegrations = async () => {
|
||||
const { data } = await apiRequest.get<SlackIntegration[]>("/api/v1/workflow-integrations/slack");
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchSlackIntegrationById = async (id?: string) => {
|
||||
const { data } = await apiRequest.get<SlackIntegration>(
|
||||
`/api/v1/workflow-integrations/slack/${id}`
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useGetSlackIntegrations = (orgId?: string) =>
|
||||
useQuery({
|
||||
queryKey: workflowIntegrationKeys.getSlackWorkflowIntegrations(orgId),
|
||||
queryFn: () => fetchSlackIntegrations(),
|
||||
enabled: Boolean(orgId)
|
||||
});
|
||||
|
||||
export const useGetSlackIntegrationById = (id?: string) =>
|
||||
useQuery({
|
||||
queryKey: workflowIntegrationKeys.getSlackWorkflowIntegration(id),
|
||||
queryFn: () => fetchSlackIntegrationById(id),
|
||||
enabled: Boolean(id)
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
export enum WorkflowIntegrationPlatform {
|
||||
SLACK = "slack"
|
||||
}
|
||||
|
||||
export type SlackIntegration = {
|
||||
id: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
teamName: string;
|
||||
};
|
||||
|
||||
export type TUpdateSlackIntegrationDTO = {
|
||||
id: string;
|
||||
orgId: string;
|
||||
slug?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type TDeleteSlackIntegrationDTO = {
|
||||
id: string;
|
||||
orgId: string;
|
||||
};
|
||||
@@ -5,11 +5,13 @@ import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
||||
import { OrgAuthTab } from "../OrgAuthTab";
|
||||
import { OrgEncryptionTab } from "../OrgEncryptionTab";
|
||||
import { OrgGeneralTab } from "../OrgGeneralTab";
|
||||
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
|
||||
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-org-general" },
|
||||
{ name: "Security", key: "tab-org-security" },
|
||||
{ name: "Encryption", key: "tab-org-encryption" },
|
||||
{ name: "Workflow Integrations", key: "workflow-integrations" },
|
||||
{ name: "Audit Log Streams", key: "tag-audit-log-streams" }
|
||||
];
|
||||
export const OrgTabGroup = () => {
|
||||
@@ -41,6 +43,9 @@ export const OrgTabGroup = () => {
|
||||
<Tab.Panel>
|
||||
<OrgEncryptionTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<OrgWorkflowIntegrationTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<AuditLogStreamsTab />
|
||||
</Tab.Panel>
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import { useState } from "react";
|
||||
import { faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types";
|
||||
|
||||
import { SlackIntegrationForm } from "./SlackIntegrationForm";
|
||||
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onToggle: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
enum WizardSteps {
|
||||
SelectPlatform = "select-platform",
|
||||
PlatformInputs = "platform-inputs"
|
||||
}
|
||||
|
||||
const PLATFORM_LIST = [
|
||||
{
|
||||
icon: faSlack,
|
||||
platform: WorkflowIntegrationPlatform.SLACK,
|
||||
title: "Slack"
|
||||
}
|
||||
];
|
||||
|
||||
export const AddWorkflowIntegrationForm = ({ isOpen, onToggle }: Props) => {
|
||||
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectPlatform);
|
||||
const [selectedPlatform, setSelectedPlatform] = useState<string | null>(null);
|
||||
|
||||
const handleFormReset = (state: boolean = false) => {
|
||||
onToggle(state);
|
||||
setWizardStep(WizardSteps.SelectPlatform);
|
||||
setSelectedPlatform(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={(state) => handleFormReset(state)}>
|
||||
<ModalContent
|
||||
title="Add a workflow integration"
|
||||
subTitle="Configure a workflow integration"
|
||||
className="my-4"
|
||||
>
|
||||
<AnimatePresence exitBeforeEnter>
|
||||
{wizardStep === WizardSteps.SelectPlatform && (
|
||||
<motion.div
|
||||
key="select-type-step"
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
>
|
||||
<div className="mb-4 text-mineshaft-300">Select a platform</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
{PLATFORM_LIST.map(({ icon, platform, title }) => (
|
||||
<div
|
||||
key={platform}
|
||||
className="flex h-28 w-32 cursor-pointer flex-col items-center space-y-4 rounded border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:border-primary/70 hover:bg-primary/10 hover:text-white"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setSelectedPlatform(platform);
|
||||
setWizardStep(WizardSteps.PlatformInputs);
|
||||
}}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
setSelectedPlatform(platform);
|
||||
setWizardStep(WizardSteps.PlatformInputs);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} size="lg" />
|
||||
<div className="whitespace-pre-wrap text-center text-sm">{title}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
{wizardStep === WizardSteps.PlatformInputs &&
|
||||
selectedPlatform === WorkflowIntegrationPlatform.SLACK && (
|
||||
<motion.div
|
||||
key="slack-platform"
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
>
|
||||
<SlackIntegrationForm onClose={() => onToggle(false)} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types";
|
||||
|
||||
import { SlackIntegrationForm } from "./SlackIntegrationForm";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
id: string;
|
||||
workflowPlatform: WorkflowIntegrationPlatform;
|
||||
onOpenChange: (state: boolean) => void;
|
||||
};
|
||||
|
||||
export const IntegrationFormDetails = ({ isOpen, id, onOpenChange, workflowPlatform }: Props) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent title="Workflow integration details">
|
||||
{workflowPlatform === WorkflowIntegrationPlatform.SLACK && (
|
||||
<SlackIntegrationForm id={id} onClose={() => onOpenChange(false)} />
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
import { faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faEllipsis, faGear, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
EmptyState,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteSlackIntegration, useGetSlackIntegrations } from "@app/hooks/api";
|
||||
import { WorkflowIntegrationPlatform } from "@app/hooks/api/workflowIntegrations/types";
|
||||
|
||||
import { AddWorkflowIntegrationForm } from "./AddWorkflowIntegrationForm";
|
||||
import { IntegrationFormDetails } from "./IntegrationFormDetails";
|
||||
|
||||
export const OrgWorkflowIntegrationTab = withPermission(
|
||||
() => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"addWorkflowIntegration",
|
||||
"integrationDetails",
|
||||
"removeIntegration"
|
||||
] as const);
|
||||
|
||||
const { currentOrg } = useOrganization();
|
||||
const { data: slackIntegrations, isLoading: isSlackIntegrationsLoading } =
|
||||
useGetSlackIntegrations(currentOrg?.id);
|
||||
const { mutateAsync: deleteSlackIntegration } = useDeleteSlackIntegration();
|
||||
|
||||
const handleRemoveIntegration = async () => {
|
||||
if (!currentOrg) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { platform, id } = popUp.removeIntegration.data;
|
||||
if (platform === WorkflowIntegrationPlatform.SLACK) {
|
||||
await deleteSlackIntegration({
|
||||
id,
|
||||
orgId: currentOrg?.id
|
||||
});
|
||||
}
|
||||
|
||||
handlePopUpClose("removeIntegration");
|
||||
createNotification({
|
||||
text: "Successfully deleted integration",
|
||||
type: "success"
|
||||
});
|
||||
};
|
||||
|
||||
const isIntegrationsLoading = isSlackIntegrationsLoading;
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Workflow Integrations</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Settings}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
handlePopUpOpen("addWorkflowIntegration");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="mb-4 text-gray-400">
|
||||
Connect Infisical to other platforms for notification and workflow integrations.
|
||||
</p>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Td>Provider</Td>
|
||||
<Td>Alias</Td>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isIntegrationsLoading && (
|
||||
<TableSkeleton columns={2} innerKey="integrations-loading" />
|
||||
)}
|
||||
{!isIntegrationsLoading && slackIntegrations && slackIntegrations.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<EmptyState title="No workflow integrations found" icon={faGear} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{slackIntegrations?.map((slackIntegration) => (
|
||||
<Tr key={slackIntegration.id}>
|
||||
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
||||
<FontAwesomeIcon icon={faSlack} />
|
||||
<div className="ml-2">SLACK</div>
|
||||
</Td>
|
||||
<Td>{slackIntegration.slug}</Td>
|
||||
<Td>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="flex justify-end hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
handlePopUpOpen("integrationDetails", {
|
||||
id: slackIntegration.id,
|
||||
platform: WorkflowIntegrationPlatform.SLACK
|
||||
});
|
||||
}}
|
||||
>
|
||||
More details
|
||||
</DropdownMenuItem>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
an={OrgPermissionSubjects.Settings}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
disabled={!isAllowed}
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
handlePopUpOpen("removeIntegration", {
|
||||
id: slackIntegration.id,
|
||||
slug: slackIntegration.slug,
|
||||
platform: WorkflowIntegrationPlatform.SLACK
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<AddWorkflowIntegrationForm
|
||||
isOpen={popUp.addWorkflowIntegration.isOpen}
|
||||
onToggle={(state) => handlePopUpToggle("addWorkflowIntegration", state)}
|
||||
/>
|
||||
<IntegrationFormDetails
|
||||
isOpen={popUp.integrationDetails?.isOpen}
|
||||
workflowPlatform={popUp.integrationDetails?.data?.platform}
|
||||
id={popUp.integrationDetails?.data?.id}
|
||||
onOpenChange={(state) => handlePopUpToggle("integrationDetails", state)}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.removeIntegration.isOpen}
|
||||
title={`Are you sure want to remove ${popUp?.removeIntegration?.data?.slug}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("removeIntegration", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleRemoveIntegration}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Settings }
|
||||
);
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/router";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import {
|
||||
fetchSlackInstallUrl,
|
||||
useGetSlackIntegrationById,
|
||||
useUpdateSlackIntegration
|
||||
} from "@app/hooks/api";
|
||||
|
||||
type Props = {
|
||||
id?: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const slackFormSchema = z.object({
|
||||
slug: z.string(),
|
||||
description: z.string().optional()
|
||||
});
|
||||
|
||||
type TSlackFormData = z.infer<typeof slackFormSchema>;
|
||||
|
||||
export const SlackIntegrationForm = ({ id, onClose }: Props) => {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = useForm<TSlackFormData>({
|
||||
resolver: zodResolver(slackFormSchema)
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const [isConnectLoading, setIsConnectLoading] = useToggle(false);
|
||||
const { currentOrg } = useOrganization();
|
||||
const { data: slackIntegration } = useGetSlackIntegrationById(id);
|
||||
const { mutateAsync: updateSlackIntegration } = useUpdateSlackIntegration();
|
||||
|
||||
useEffect(() => {
|
||||
if (slackIntegration) {
|
||||
setValue("slug", slackIntegration.slug);
|
||||
setValue("description", slackIntegration.description ?? "");
|
||||
}
|
||||
}, [slackIntegration]);
|
||||
|
||||
const triggerSlackInstall = async (slug: string, description?: string) => {
|
||||
setIsConnectLoading.on();
|
||||
const slackInstallUrl = await fetchSlackInstallUrl({
|
||||
slug,
|
||||
description
|
||||
});
|
||||
if (slackInstallUrl) {
|
||||
router.push(slackInstallUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSlackFormSubmit = async ({ slug, description }: TSlackFormData) => {
|
||||
if (id && slackIntegration) {
|
||||
if (!currentOrg) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateSlackIntegration({
|
||||
id,
|
||||
orgId: currentOrg?.id,
|
||||
slug,
|
||||
description
|
||||
});
|
||||
|
||||
onClose();
|
||||
createNotification({
|
||||
text: "Successfully updated Slack integration",
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
await triggerSlackInstall(slug, description);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleSlackFormSubmit)} autoComplete="off">
|
||||
<Controller
|
||||
control={control}
|
||||
name="slug"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Alias" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input placeholder="" {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Description" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input placeholder="" {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{slackIntegration && (
|
||||
<FormControl label="Slack workspace">
|
||||
<Input value={slackIntegration?.teamName} isReadOnly className="bg-white/[0.07]" />
|
||||
</FormControl>
|
||||
)}
|
||||
<div className="mt-6 flex items-center space-x-4">
|
||||
<Button type="submit" isLoading={isSubmitting || isConnectLoading} isDisabled={!isDirty}>
|
||||
{slackIntegration ? "Save" : "Connect"}
|
||||
</Button>
|
||||
<Button variant="outline_bg" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user