feat: switched to a wizard for create identity in project

This commit is contained in:
=
2025-11-06 12:40:54 +05:30
parent 4bb13d592f
commit d13a0dc5b6
5 changed files with 119 additions and 98 deletions

View File

@@ -1,18 +1,19 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import {
faArrowDown,
faArrowUp,
faChevronDown,
faCircleXmark,
faClock,
faEllipsisV,
faLink,
faMagnifyingGlass,
faPlus,
faServer
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import { LinkIcon, PlusIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
@@ -30,6 +31,8 @@ import {
HoverCardTrigger,
IconButton,
Input,
Modal,
ModalContent,
Pagination,
Spinner,
Table,
@@ -79,12 +82,20 @@ import { ProjectLinkIdentityModal } from "./components/ProjectLinkIdentityModal"
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
enum WizardSteps {
SelectAction = "select-action",
LinkIdentity = "link-identity",
ProjectIdentity = "project-identity"
}
export const IdentityTab = withProjectPermission(
() => {
const { currentProject, projectId } = useProject();
const navigate = useNavigate();
const { isSubOrganization, currentOrg } = useOrganization();
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectAction);
const {
offset,
limit,
@@ -133,7 +144,6 @@ export const IdentityTab = withProjectPermission(
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"createIdentity",
"linkIdentity",
"deleteIdentity",
"upgradePlan",
"addOptions"
@@ -201,37 +211,6 @@ export const IdentityTab = withProjectPermission(
</Button>
)}
</ProjectPermissionCan>
<DropdownMenu
open={popUp.addOptions.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addOptions", isOpen)}
>
<DropdownMenuTrigger>
<Button variant="outline_bg" className="rounded-l-none border-l-mineshaft-800 px-3">
<FontAwesomeIcon icon={faChevronDown} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={6} className="p-1">
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<Button
variant="outline_bg"
className="w-full"
isDisabled={!isAllowed}
leftIcon={<FontAwesomeIcon icon={faLink} />}
onClick={() => {
handlePopUpOpen("linkIdentity");
handlePopUpClose("addOptions");
}}
>
Assign Org Identity
</Button>
)}
</ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<Input
@@ -494,11 +473,94 @@ export const IdentityTab = withProjectPermission(
/>
)}
</TableContainer>
<ProjectIdentityModal
<Modal
isOpen={popUp.createIdentity.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("createIdentity", isOpen)}
/>
<ProjectLinkIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
onOpenChange={(open) => {
handlePopUpToggle("createIdentity", open);
if (!open) setWizardStep(WizardSteps.SelectAction);
}}
>
<ModalContent
bodyClassName="overflow-visible"
title="Add Project Identity"
subTitle="Create a new identity or assign an existing identity"
>
<AnimatePresence mode="wait">
{wizardStep === WizardSteps.SelectAction && (
<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="cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(WizardSteps.ProjectIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(WizardSteps.ProjectIdentity);
}
}}
>
<div className="flex items-center gap-2">
<PlusIcon size="1rem" />
<div>Create New Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Create a new machine identity specifically for this project. This identity
will be managed at the project-level.
</div>
</div>
<div
className="mt-4 cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(WizardSteps.LinkIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(WizardSteps.LinkIdentity);
}
}}
>
<div className="flex items-center gap-2">
<LinkIcon size="1rem" />
<div>Assign Existing Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Assign an existing identity from your organization or namespace to this
project. The identity will continue to be managed at its original scope.
</div>
</div>
</motion.div>
)}
{wizardStep === WizardSteps.ProjectIdentity && (
<motion.div
key="identity-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<ProjectIdentityModal onClose={() => handlePopUpClose("createIdentity")} />
</motion.div>
)}
{wizardStep === WizardSteps.LinkIdentity && (
<motion.div
key="link-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<ProjectLinkIdentityModal handlePopUpToggle={handlePopUpToggle} />
</motion.div>
)}
</AnimatePresence>
</ModalContent>
</Modal>
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure you want to remove ${

View File

@@ -13,8 +13,6 @@ import {
FormLabel,
IconButton,
Input,
Modal,
ModalContent,
Switch
} from "@app/components/v2";
import { useProject } from "@app/context";
@@ -49,7 +47,7 @@ type ContentProps = {
identity?: TProjectIdentity;
};
const Content = ({ onClose, identity }: ContentProps) => {
export const ProjectIdentityModal = ({ onClose, identity }: ContentProps) => {
const navigate = useNavigate();
const { currentProject } = useProject();
@@ -289,22 +287,3 @@ const Content = ({ onClose, identity }: ContentProps) => {
</form>
);
};
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
identity?: TProjectIdentity;
};
export const ProjectIdentityModal = ({ isOpen, onOpenChange, identity }: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={(open) => onOpenChange(open)}>
<ModalContent
bodyClassName="overflow-visible"
title={`${identity ? "Update" : "Create"} Project Identity`}
>
<Content identity={identity} onClose={() => onOpenChange(false)} />
</ModalContent>
</Modal>
);
};

View File

@@ -5,15 +5,7 @@ import { useQuery } from "@tanstack/react-query";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
FilterableSelect,
FormControl,
Modal,
ModalClose,
ModalContent,
Spinner
} from "@app/components/v2";
import { Button, FilterableSelect, FormControl, ModalClose, Spinner } from "@app/components/v2";
import { useProject } from "@app/context";
import {
projectIdentityMembershipQuery,
@@ -34,11 +26,10 @@ const schema = z.object({
export type FormData = z.infer<typeof schema>;
type Props = {
popUp: UsePopUpState<["linkIdentity"]>;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["linkIdentity"]>, state?: boolean) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["createIdentity"]>, state?: boolean) => void;
};
const Content = ({ popUp, handlePopUpToggle }: Props) => {
export const ProjectLinkIdentityModal = ({ handlePopUpToggle }: Props) => {
const { projectId } = useProject();
// const [searchValue, setSearchValue] = useState("");
@@ -107,7 +98,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
id: nextAvailableMembership?.id
}
});
handlePopUpToggle("linkIdentity", false);
handlePopUpToggle("createIdentity", false);
};
if (isMembershipsLoading || isRolesLoading)
@@ -168,7 +159,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{popUp?.linkIdentity?.data ? "Update" : "Link"}
Link
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
@@ -179,22 +170,3 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
</form>
);
};
export const ProjectLinkIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
return (
<Modal
isOpen={popUp?.linkIdentity?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("linkIdentity", isOpen);
}}
>
<ModalContent
title="Assign Existing Identity"
subTitle="Assign an existing identity from the parent organization to this project. The identity will continue to be managed at its original scope."
bodyClassName="overflow-visible"
>
<Content popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</ModalContent>
</Modal>
);
};

View File

@@ -28,7 +28,6 @@ import { usePopUp } from "@app/hooks";
import {
useAssumeProjectPrivileges,
useDeleteProjectIdentityMembership,
useGetProjectIdentityMembership,
useGetProjectIdentityMembershipV2
} from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";

View File

@@ -22,6 +22,8 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
IconButton,
Modal,
ModalContent,
Tag,
Tooltip
} from "@app/components/v2";
@@ -223,11 +225,18 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
)}
</div>
</div>
<ProjectIdentityModal
identity={identity}
<Modal
isOpen={popUp.editIdentity.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("editIdentity", isOpen)}
/>
onOpenChange={(open) => handlePopUpToggle("editIdentity", open)}
>
<ModalContent bodyClassName="overflow-visible" title="Edit Project Identity">
<ProjectIdentityModal
identity={identity}
onClose={() => handlePopUpToggle("editIdentity", false)}
/>
</ModalContent>
</Modal>
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure you want to delete ${identity.name}?`}