smaller ui improvements

This commit is contained in:
Daniel Hougaard
2025-05-01 19:50:50 +04:00
parent 75cb259c51
commit 550cb2b5ec
4 changed files with 146 additions and 114 deletions
@@ -93,8 +93,6 @@ export const WorkflowIntegrationTab = () => {
<THead> <THead>
<Tr> <Tr>
<Td>Provider</Td> <Td>Provider</Td>
<Td>Access Request Notifications</Td>
<Td>Secret Request Notifications</Td>
<Td>Access Request Notifications Destination</Td> <Td>Access Request Notifications Destination</Td>
<Td>Secret Request Notifications Destination</Td> <Td>Secret Request Notifications Destination</Td>
<Td /> <Td />
@@ -1,5 +1,6 @@
/* eslint-disable no-nested-ternary */
import { BsMicrosoftTeams } from "react-icons/bs"; import { BsMicrosoftTeams } from "react-icons/bs";
import { faCheck, faEllipsis, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -34,10 +35,11 @@ type Props = {
export const MicrosoftTeamsConfigRow = ({ handlePopUpOpen }: Props) => { export const MicrosoftTeamsConfigRow = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data: microsoftTeamsConfig } = useGetWorkspaceWorkflowIntegrationConfig({ const { data: microsoftTeamsConfig, isPending: isMicrosoftTeamsConfigLoading } =
workspaceId: currentWorkspace?.id ?? "", useGetWorkspaceWorkflowIntegrationConfig({
integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS workspaceId: currentWorkspace?.id ?? "",
}); integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS
});
const { data: microsoftTeamsChannels, isPending: isMicrosoftTeamsChannelsLoading } = const { data: microsoftTeamsChannels, isPending: isMicrosoftTeamsChannelsLoading } =
useGetMicrosoftTeamsIntegrationTeams(microsoftTeamsConfig?.integrationId); useGetMicrosoftTeamsIntegrationTeams(microsoftTeamsConfig?.integrationId);
@@ -51,6 +53,8 @@ export const MicrosoftTeamsConfigRow = ({ handlePopUpOpen }: Props) => {
return null; return null;
} }
const isLoadingConfig = isMicrosoftTeamsChannelsLoading || isMicrosoftTeamsConfigLoading;
return ( return (
<Tr> <Tr>
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis"> <Td className="flex max-w-xs items-center overflow-hidden text-ellipsis">
@@ -60,39 +64,33 @@ export const MicrosoftTeamsConfigRow = ({ handlePopUpOpen }: Props) => {
</div> </div>
</Td> </Td>
<Td> <Td>
{microsoftTeamsConfig.isAccessRequestNotificationEnabled ? ( {microsoftTeamsConfig.isAccessRequestNotificationEnabled &&
<FontAwesomeIcon icon={faCheck} className="text-green-500" /> !isLoadingConfig &&
) : ( microsoftTeamsConfig.accessRequestChannels?.channelIds?.length > 0 ? (
<FontAwesomeIcon icon={faXmark} className="text-red-500" />
)}
</Td>
<Td>
{microsoftTeamsConfig.isSecretRequestNotificationEnabled ? (
<FontAwesomeIcon icon={faCheck} className="text-green-500" />
) : (
<FontAwesomeIcon icon={faXmark} className="text-red-500" />
)}
</Td>
<Td>
{isMicrosoftTeamsChannelsLoading ? (
<Spinner size="xs" />
) : (
<Badge> <Badge>
{(microsoftTeamsConfig.accessRequestChannels?.channelIds || []) {microsoftTeamsConfig.accessRequestChannels.channelIds
?.map((channel) => microsoftTeamsChannelIdToName[channel]) .map((channel) => microsoftTeamsChannelIdToName[channel])
.join(", ")} .join(", ")}
</Badge> </Badge>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : (
<Badge variant="danger">Disabled</Badge>
)} )}
</Td> </Td>
<Td> <Td>
{isMicrosoftTeamsChannelsLoading ? ( {microsoftTeamsConfig.isSecretRequestNotificationEnabled &&
<Spinner size="xs" /> !isLoadingConfig &&
) : ( microsoftTeamsConfig.secretRequestChannels?.channelIds?.length > 0 ? (
<Badge> <Badge>
{(microsoftTeamsConfig.secretRequestChannels?.channelIds || []) {microsoftTeamsConfig.secretRequestChannels.channelIds
?.map((channel) => microsoftTeamsChannelIdToName[channel]) .map((channel) => microsoftTeamsChannelIdToName[channel])
.join(", ")} .join(", ")}
</Badge> </Badge>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : (
<Badge variant="danger">Disabled</Badge>
)} )}
</Td> </Td>
@@ -104,6 +102,25 @@ export const MicrosoftTeamsConfigRow = ({ handlePopUpOpen }: Props) => {
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Settings}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("editIntegration", {
integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Settings}> <OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Settings}>
{(isAllowed) => ( {(isAllowed) => (
<DropdownMenuItem <DropdownMenuItem
@@ -124,25 +141,6 @@ export const MicrosoftTeamsConfigRow = ({ handlePopUpOpen }: Props) => {
</DropdownMenuItem> </DropdownMenuItem>
)} )}
</OrgPermissionCan> </OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Settings}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("editIntegration", {
integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</Td> </Td>
@@ -17,7 +17,8 @@ import {
Input, Input,
Select, Select,
SelectItem, SelectItem,
Switch Switch,
Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { import {
@@ -37,18 +38,22 @@ const formSchema = z
microsoftTeamsIntegrationId: z.string(), microsoftTeamsIntegrationId: z.string(),
isSecretRequestNotificationEnabled: z.boolean(), isSecretRequestNotificationEnabled: z.boolean(),
isAccessRequestNotificationEnabled: z.boolean(), isAccessRequestNotificationEnabled: z.boolean(),
secretRequestChannels: z.object({ secretRequestChannels: z
teamId: z.string(), .object({
channelIds: z.string().array() teamId: z.string(),
}), channelIds: z.string().array()
accessRequestChannels: z.object({ })
teamId: z.string(), .optional(),
channelIds: z.string().array() accessRequestChannels: z
}) .object({
teamId: z.string(),
channelIds: z.string().array()
})
.optional()
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
if (data.isSecretRequestNotificationEnabled) { if (data.isSecretRequestNotificationEnabled) {
if (!data.secretRequestChannels.teamId) { if (!data?.secretRequestChannels?.teamId) {
ctx.addIssue({ ctx.addIssue({
path: ["secretRequestChannels", "teamId"], path: ["secretRequestChannels", "teamId"],
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@@ -56,7 +61,7 @@ const formSchema = z
}); });
} }
if (!data.secretRequestChannels.channelIds.length) { if (!data?.secretRequestChannels?.channelIds?.length) {
ctx.addIssue({ ctx.addIssue({
path: ["secretRequestChannels", "channelIds"], path: ["secretRequestChannels", "channelIds"],
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@@ -66,7 +71,7 @@ const formSchema = z
} }
if (data.isAccessRequestNotificationEnabled) { if (data.isAccessRequestNotificationEnabled) {
if (!data.accessRequestChannels.teamId) { if (!data?.accessRequestChannels?.teamId) {
ctx.addIssue({ ctx.addIssue({
path: ["accessRequestChannels", "teamId"], path: ["accessRequestChannels", "teamId"],
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@@ -74,7 +79,7 @@ const formSchema = z
}); });
} }
if (!data.accessRequestChannels.channelIds.length) { if (!data?.accessRequestChannels?.channelIds?.length) {
ctx.addIssue({ ctx.addIssue({
path: ["accessRequestChannels", "channelIds"], path: ["accessRequestChannels", "channelIds"],
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@@ -108,7 +113,8 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
watch, watch,
handleSubmit, handleSubmit,
setValue, setValue,
formState: { isDirty, isSubmitting } getValues,
formState: { isDirty, isSubmitting, errors }
} = useForm<TMicrosoftTeamsConfigForm>({ } = useForm<TMicrosoftTeamsConfigForm>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
@@ -210,13 +216,20 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
if (microsoftTeamsConfig.integration === WorkflowIntegrationPlatform.MICROSOFT_TEAMS) { if (microsoftTeamsConfig.integration === WorkflowIntegrationPlatform.MICROSOFT_TEAMS) {
if (microsoftTeamsConfig.secretRequestChannels) { if (microsoftTeamsConfig.secretRequestChannels) {
setValue("secretRequestChannels", microsoftTeamsConfig.secretRequestChannels); if (Object.entries(microsoftTeamsConfig.accessRequestChannels).length) {
setValue("accessRequestChannels", microsoftTeamsConfig.accessRequestChannels); setValue("accessRequestChannels", microsoftTeamsConfig.accessRequestChannels);
}
if (Object.entries(microsoftTeamsConfig.secretRequestChannels).length) {
setValue("secretRequestChannels", microsoftTeamsConfig.secretRequestChannels);
}
} }
} }
} }
}, [microsoftTeamsConfig]); }, [microsoftTeamsConfig]);
console.log("values", getValues());
console.log("errors", errors);
return ( return (
<form onSubmit={handleSubmit(handleIntegrationSave)}> <form onSubmit={handleSubmit(handleIntegrationSave)}>
<div className="flex w-full flex-col justify-start"> <div className="flex w-full flex-col justify-start">
@@ -235,7 +248,20 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
{...field} {...field}
isDisabled={!isAllowed} isDisabled={!isAllowed}
placeholder="None" placeholder="None"
onValueChange={onChange} onValueChange={(v) => {
onChange(v);
setValue("isAccessRequestNotificationEnabled", false);
setValue("isSecretRequestNotificationEnabled", false);
setValue("accessRequestChannels", {
teamId: "",
channelIds: []
});
setValue("secretRequestChannels", {
teamId: "",
channelIds: []
});
}}
className="w-full" className="w-full"
defaultValue={microsoftTeamsConfig?.integrationId} defaultValue={microsoftTeamsConfig?.integrationId}
> >
@@ -522,6 +548,7 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
colorSchema="secondary" colorSchema="secondary"
className="mt-4" className="mt-4"
type="submit" type="submit"
disabled={!isDirty}
isDisabled={!isDirty} isDisabled={!isDirty}
isLoading={isSubmitting} isLoading={isSubmitting}
> >
@@ -1,5 +1,6 @@
/* eslint-disable no-nested-ternary */
import { BsSlack } from "react-icons/bs"; import { BsSlack } from "react-icons/bs";
import { faCheck, faEllipsis, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -10,6 +11,7 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
Spinner,
Td, Td,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
@@ -33,12 +35,15 @@ type Props = {
export const SlackConfigRow = ({ handlePopUpOpen }: Props) => { export const SlackConfigRow = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data: slackConfig } = useGetWorkspaceWorkflowIntegrationConfig({ const { data: slackConfig, isPending: isSlackConfigLoading } =
workspaceId: currentWorkspace?.id ?? "", useGetWorkspaceWorkflowIntegrationConfig({
integration: WorkflowIntegrationPlatform.SLACK workspaceId: currentWorkspace?.id ?? "",
}); integration: WorkflowIntegrationPlatform.SLACK
});
const { data: slackChannels } = useGetSlackIntegrationChannels(slackConfig?.integrationId); const { data: slackChannels, isPending: isSlackChannelsLoading } = useGetSlackIntegrationChannels(
slackConfig?.integrationId
);
const slackChannelIdToName = Object.fromEntries( const slackChannelIdToName = Object.fromEntries(
(slackChannels || []).map((channel) => [channel.id, channel.name]) (slackChannels || []).map((channel) => [channel.id, channel.name])
); );
@@ -47,6 +52,8 @@ export const SlackConfigRow = ({ handlePopUpOpen }: Props) => {
return null; return null;
} }
const isLoadingConfig = isSlackChannelsLoading || isSlackConfigLoading;
return ( return (
<Tr> <Tr>
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis"> <Td className="flex max-w-xs items-center overflow-hidden text-ellipsis">
@@ -56,35 +63,37 @@ export const SlackConfigRow = ({ handlePopUpOpen }: Props) => {
</div> </div>
</Td> </Td>
<Td> <Td>
{slackConfig.isAccessRequestNotificationEnabled ? ( {slackConfig.isAccessRequestNotificationEnabled &&
<FontAwesomeIcon icon={faCheck} className="text-green-500" /> !isLoadingConfig &&
slackConfig.accessRequestChannels.length > 0 ? (
<Badge>
{slackConfig.accessRequestChannels
.split(", ")
.map((channel) => slackChannelIdToName[channel])
.join(", ")}
</Badge>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : ( ) : (
<FontAwesomeIcon icon={faXmark} className="text-red-500" /> <Badge variant="danger">Disabled</Badge>
)} )}
</Td> </Td>
<Td> <Td>
{slackConfig.isSecretRequestNotificationEnabled ? ( {slackConfig.isSecretRequestNotificationEnabled &&
<FontAwesomeIcon icon={faCheck} className="text-green-500" /> !isLoadingConfig &&
slackConfig.secretRequestChannels.length > 0 ? (
<Badge>
{slackConfig.secretRequestChannels
.split(", ")
.map((channel) => slackChannelIdToName[channel])
.join(", ")}
</Badge>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : ( ) : (
<FontAwesomeIcon icon={faXmark} className="text-red-500" /> <Badge variant="danger">Disabled</Badge>
)} )}
</Td> </Td>
<Td>
<Badge>
{slackConfig.accessRequestChannels
.split(", ")
.map((channel) => slackChannelIdToName[channel])
.join(", ")}
</Badge>
</Td>
<Td>
<Badge>
{slackConfig.secretRequestChannels
.split(", ")
.map((channel) => slackChannelIdToName[channel])
.join(", ")}
</Badge>
</Td>
<Td> <Td>
<DropdownMenu> <DropdownMenu>
@@ -94,6 +103,25 @@ export const SlackConfigRow = ({ handlePopUpOpen }: Props) => {
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Settings}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("editIntegration", {
integration: WorkflowIntegrationPlatform.SLACK
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Settings}> <OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Settings}>
{(isAllowed) => ( {(isAllowed) => (
<DropdownMenuItem <DropdownMenuItem
@@ -114,25 +142,6 @@ export const SlackConfigRow = ({ handlePopUpOpen }: Props) => {
</DropdownMenuItem> </DropdownMenuItem>
)} )}
</OrgPermissionCan> </OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Settings}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("editIntegration", {
integration: WorkflowIntegrationPlatform.SLACK
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</Td> </Td>