diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx index 23f7cc940..db00712a0 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx @@ -4,7 +4,14 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2"; +import { + Button, + FilterableSelect, + FormControl, + Modal, + ModalClose, + ModalContent +} from "@app/components/v2"; import { useOrganization, useWorkspace } from "@app/context"; import { useAddIdentityToWorkspace, @@ -16,8 +23,8 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z .object({ - projectId: z.string(), - role: z.string() + project: z.object({ name: z.string(), id: z.string() }), + role: z.object({ name: z.string(), slug: z.string() }) }) .required(); @@ -32,7 +39,9 @@ type Props = { ) => void; }; -export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle }: Props) => { +// TODO: eventually refactor to support adding to multiple projects at once? would lose role granularity unique to project + +const Content = ({ identityId, handlePopUpToggle }: Omit) => { const { currentOrg } = useOrganization(); const { workspaces } = useWorkspace(); const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace(); @@ -47,10 +56,10 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle resolver: zodResolver(schema) }); - const projectId = watch("projectId"); + const projectId = watch("project")?.id; const { data: projectMemberships } = useGetIdentityProjectMemberships(identityId); - const { data: project } = useGetWorkspaceById(projectId); - const { data: roles } = useGetProjectRoles(project?.id ?? ""); + const { data: project, isLoading: isProjectLoading } = useGetWorkspaceById(projectId); + const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(project?.id ?? ""); const filteredWorkspaces = useMemo(() => { const wsWorkspaceIds = new Map(); @@ -64,12 +73,12 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle ); }, [workspaces, projectMemberships]); - const onFormSubmit = async ({ projectId: workspaceId, role }: FormData) => { + const onFormSubmit = async ({ project: selectedProject, role }: FormData) => { try { await addIdentityToWorkspace({ - workspaceId, + workspaceId: selectedProject.id, identityId, - role: role || undefined + role: role.slug || undefined }); createNotification({ @@ -91,87 +100,85 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle } }; + const isProjectSelected = Boolean(projectId); + + return ( +
+ ( + + option.id} + getOptionLabel={(option) => option.name} + isLoading={isProjectSelected && isProjectLoading} + /> + + )} + /> + ( + + option.slug} + getOptionLabel={(option) => option.name} + /> + + )} + /> +
+ + + + +
+ + ); +}; + +export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle }: Props) => { return ( { handlePopUpToggle("addIdentityToProject", isOpen); - reset(); }} > - -
- ( - - - - )} - /> - ( - - - - )} - /> -
- - -
- + +
); diff --git a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx index 6531fdc0f..4ac7452cf 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx @@ -42,7 +42,7 @@ export const IdentitySection = withPermission( ? subscription.identitiesUsed < subscription.identityLimit : true; - const isEnterprise = subscription?.slug === "enterprise" + const isEnterprise = subscription?.slug === "enterprise"; const onDeleteIdentitySubmit = async (identityId: string) => { try { @@ -105,7 +105,7 @@ export const IdentitySection = withPermission( }} isDisabled={!isAllowed} > - Create identity + Create Identity )} diff --git a/frontend/src/views/Project/MembersPage/components/IdentityTab/IdentityTab.tsx b/frontend/src/views/Project/MembersPage/components/IdentityTab/IdentityTab.tsx index 260b08411..defd32863 100644 --- a/frontend/src/views/Project/MembersPage/components/IdentityTab/IdentityTab.tsx +++ b/frontend/src/views/Project/MembersPage/components/IdentityTab/IdentityTab.tsx @@ -181,7 +181,7 @@ export const IdentityTab = withProjectPermission( onClick={() => handlePopUpOpen("identity")} isDisabled={!isAllowed} > - Add identity + Add Identity )} diff --git a/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityModal.tsx b/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityModal.tsx index 811958d37..1b39ea830 100644 --- a/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityModal.tsx +++ b/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityModal.tsx @@ -1,12 +1,19 @@ -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import Link from "next/link"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Modal, ModalClose, ModalContent } from "@app/components/v2"; -import { ComboBox } from "@app/components/v2/ComboBox"; +import { + Button, + FilterableSelect, + FormControl, + Modal, + ModalClose, + ModalContent, + Spinner +} from "@app/components/v2"; import { useOrganization, useWorkspace } from "@app/context"; import { useAddIdentityToWorkspace, @@ -16,37 +23,30 @@ import { } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; -const schema = yup - .object({ - identity: yup.object({ - id: yup.string().required("Identity id is required"), - name: yup.string().required("Identity name is required") - }), - role: yup.object({ - slug: yup.string().required("role slug is required"), - name: yup.string().required("role name is required") - }) - }) - .required(); +const schema = z.object({ + identity: z.object({ name: z.string(), id: z.string() }), + role: z.object({ name: z.string(), slug: z.string() }) +}); -export type FormData = yup.InferType; +export type FormData = z.infer; type Props = { popUp: UsePopUpState<["identity"]>; handlePopUpToggle: (popUpName: keyof UsePopUpState<["identity"]>, state?: boolean) => void; }; -export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { +const Content = ({ popUp, handlePopUpToggle }: Props) => { const { currentOrg } = useOrganization(); const { currentWorkspace } = useWorkspace(); const organizationId = currentOrg?.id || ""; const workspaceId = currentWorkspace?.id || ""; - const { data: identityMembershipOrgsData } = useGetIdentityMembershipOrgs({ - organizationId, - limit: 20000 // TODO: this is temp to preserve functionality for larger projects, will replace with combobox in separate PR - }); + const { data: identityMembershipOrgsData, isLoading: isMembershipsLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 // TODO: this is temp to preserve functionality for larger projects, will replace with combobox in separate PR + }); const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships; const { data: identityMembershipsData } = useGetWorkspaceIdentityMemberships({ workspaceId, @@ -54,11 +54,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { }); const identityMemberships = identityMembershipsData?.identityMemberships; - const { - data: roles, - isLoading: isRolesLoading, - isFetched: isRolesFetched - } = useGetProjectRoles(workspaceId); + const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(workspaceId); const { mutateAsync: addIdentityToWorkspaceMutateAsync } = useAddIdentityToWorkspace(); @@ -76,18 +72,11 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { control, handleSubmit, reset, - setValue, formState: { isSubmitting } } = useForm({ - resolver: yupResolver(schema) + resolver: zodResolver(schema) }); - useEffect(() => { - if (!isRolesFetched || !roles) return; - - setValue("role", { name: roles[0]?.name, slug: roles[0]?.slug }); - }, [isRolesFetched, roles]); - const onFormSubmit = async ({ identity, role }: FormData) => { try { await addIdentityToWorkspaceMutateAsync({ @@ -125,104 +114,93 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { } }; + if (isMembershipsLoading || isRolesLoading) + return ( +
+ +
+ ); + + return filteredIdentityMembershipOrgs.length ? ( +
+ ( + + membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + + )} + /> + ( + + option.slug} + getOptionLabel={(option) => option.name} + /> + + )} + /> +
+ + + + +
+ + ) : ( +
+
+ All identities in your organization have already been added to this project. +
+ + + +
+ ); +}; + +export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { return ( { handlePopUpToggle("identity", isOpen); - reset(); }} > - {filteredIdentityMembershipOrgs.length ? ( -
- ( - - onChange({ id: value.id, name: value.name })} - displayValue={(el) => el.name} - onFilter={({ value }, filterQuery) => - value.name.toLowerCase().includes(filterQuery.toLowerCase()) - } - items={filteredIdentityMembershipOrgs.map(({ identity }) => ({ - key: identity.id, - value: { id: identity.id, name: identity.name }, - label: identity.name - }))} - /> - - )} - /> - ( - - onChange({ slug: value.slug, name: value.name })} - displayValue={(el) => el.name} - onFilter={({ value }, filterQuery) => - value.name.toLowerCase().includes(filterQuery.toLowerCase()) - } - items={(roles || []).map(({ slug, name }) => ({ - key: slug, - value: { slug, name }, - label: name - }))} - /> - - )} - /> -
- - - - -
- - ) : ( -
-
- All identities in your organization have already been added to this project. -
- - - -
- )} +
);