From e4de5e62c9630ed609c94b2cdee51ceb8f1dda4b Mon Sep 17 00:00:00 2001 From: Dakshesh Jain Date: Wed, 7 Dec 2022 10:15:57 +0530 Subject: [PATCH 1/2] fix: projects page flickering, mutation of workspace members --- .../components/project/CreateProjectModal.tsx | 55 +++++++++++++++++-- .../project/SendProjectInvitationModal.tsx | 2 +- .../issue-detail/IssueDetailSidebar.tsx | 2 +- .../components/project/memberInvitations.tsx | 34 +++++++++--- apps/app/constants/fetch-keys.ts | 2 +- apps/app/layouts/Navbar/Sidebar.tsx | 17 +++++- apps/app/lib/services/project.service.ts | 2 +- .../pages/projects/[projectId]/members.tsx | 6 +- .../pages/projects/[projectId]/settings.tsx | 2 +- apps/app/pages/workspace/members.tsx | 6 +- 10 files changed, 101 insertions(+), 27 deletions(-) diff --git a/apps/app/components/project/CreateProjectModal.tsx b/apps/app/components/project/CreateProjectModal.tsx index 2010170c7..6402829dc 100644 --- a/apps/app/components/project/CreateProjectModal.tsx +++ b/apps/app/components/project/CreateProjectModal.tsx @@ -1,14 +1,15 @@ import React, { useState, useEffect, useCallback } from "react"; // swr -import { mutate } from "swr"; +import useSWR, { mutate } from "swr"; // react hook form import { useForm } from "react-hook-form"; // headless import { Dialog, Transition } from "@headlessui/react"; // services import projectServices from "lib/services/project.service"; +import workspaceService from "lib/services/workspace.service"; // fetch keys -import { PROJECTS_LIST } from "constants/fetch-keys"; +import { PROJECTS_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys"; // hooks import useUser from "lib/hooks/useUser"; import useToast from "lib/hooks/useToast"; @@ -17,7 +18,7 @@ import { Button, Input, TextArea, Select } from "ui"; // common import { debounce } from "constants/common"; // types -import { IProject } from "types"; +import { IProject, WorkspaceMember } from "types"; type Props = { isOpen: boolean; @@ -31,6 +32,23 @@ const defaultValues: Partial = { description: "", }; +const IsGuestCondition: React.FC<{ + setIsOpen: React.Dispatch>; +}> = ({ setIsOpen }) => { + const { setToastAlert } = useToast(); + + useEffect(() => { + setIsOpen(false); + setToastAlert({ + title: "Error", + type: "error", + message: "You don't have permission to create project.", + }); + }, []); + + return null; +}; + const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => { const handleClose = () => { setIsOpen(false); @@ -40,7 +58,12 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => { }, 500); }; - const { activeWorkspace } = useUser(); + const { activeWorkspace, user } = useUser(); + + const { data: workspaceMembers } = useSWR( + activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null, + activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null + ); const { setToastAlert } = useToast(); @@ -56,6 +79,8 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => { setValue, } = useForm({ defaultValues, + reValidateMode: "onChange", + mode: "all", }); const onSubmit = async (formData: IProject) => { @@ -77,6 +102,15 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => { handleClose(); }) .catch((err) => { + if (err.status === 403) { + setToastAlert({ + title: "Error", + type: "error", + message: "You don't have permission to create project.", + }); + handleClose(); + return; + } Object.keys(err).map((key) => { const errorMessages = err[key]; setError(key as keyof IProject, { @@ -105,6 +139,15 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => { } }, [projectName, projectIdentifier, setValue, isChangeIdentifierRequired]); + if (workspaceMembers) { + const isMember = workspaceMembers.find((member) => member.member.id === user?.id); + const isGuest = workspaceMembers.find( + (member) => member.member.id === user?.id && member.role === 5 + ); + + if ((!isMember || isGuest) && isOpen) return ; + } + return ( @@ -203,8 +246,8 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => { message: "Identifier must at least be of 1 character", }, maxLength: { - value: 9, - message: "Identifier must at most be of 9 characters", + value: 5, + message: "Identifier must at most be of 5 characters", }, }} /> diff --git a/apps/app/components/project/SendProjectInvitationModal.tsx b/apps/app/components/project/SendProjectInvitationModal.tsx index a01d36658..7bac94fa2 100644 --- a/apps/app/components/project/SendProjectInvitationModal.tsx +++ b/apps/app/components/project/SendProjectInvitationModal.tsx @@ -50,7 +50,7 @@ const SendProjectInvitationModal: React.FC = ({ isOpen, setIsOpen, member const { setToastAlert } = useToast(); const { data: people } = useSWR( - activeWorkspace ? WORKSPACE_MEMBERS : null, + activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null, activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null ); diff --git a/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx b/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx index 68f2f91eb..9516c4238 100644 --- a/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx +++ b/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx @@ -68,7 +68,7 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta ); const { data: people } = useSWR( - activeWorkspace ? WORKSPACE_MEMBERS : null, + activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null, activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null ); diff --git a/apps/app/components/project/memberInvitations.tsx b/apps/app/components/project/memberInvitations.tsx index dc7f3af69..46804695f 100644 --- a/apps/app/components/project/memberInvitations.tsx +++ b/apps/app/components/project/memberInvitations.tsx @@ -3,10 +3,14 @@ import React, { useState } from "react"; // next import Link from "next/link"; import useSWR from "swr"; -import _ from "lodash"; +// hooks import useUser from "lib/hooks/useUser"; // Services import projectService from "lib/services/project.service"; +// fetch keys +import { PROJECT_MEMBERS } from "constants/fetch-keys"; +// commons +import { renderShortNumericDateFormat } from "constants/common"; // icons import { CalendarDaysIcon, @@ -17,25 +21,41 @@ import { PlusIcon, TrashIcon, } from "@heroicons/react/24/outline"; -import { renderShortNumericDateFormat } from "constants/common"; +// types +import type { IProject } from "types"; +type Props = { + project: IProject; + slug: string; + invitationsRespond: string[]; + handleInvitation: (project_invitation: any, action: "accepted" | "withdraw") => void; + setDeleteProject: React.Dispatch>; +}; -const ProjectMemberInvitations = ({ +const ProjectMemberInvitations: React.FC = ({ project, slug, invitationsRespond, handleInvitation, setDeleteProject, -}: any) => { +}) => { const { user } = useUser(); - const { data: members } = useSWR("PROJECT_MEMBERS", () => + + const { data: members } = useSWR(PROJECT_MEMBERS(project.id), () => projectService.projectMembers(slug, project.id) ); - const isMember = - _.filter(members, (item: any) => item.member.id === (user as any).id).length === 1; + const isMember = members?.some((item: any) => item.member.id === (user as any)?.id); const [selected, setSelected] = useState(false); + if (!members) { + return ( +
+
+
+ ); + } + return ( <>
`WORKSPACE_MEMBERS_${workspaceSlug}`; export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS"; export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION"; diff --git a/apps/app/layouts/Navbar/Sidebar.tsx b/apps/app/layouts/Navbar/Sidebar.tsx index eb90d65bc..0c299860e 100644 --- a/apps/app/layouts/Navbar/Sidebar.tsx +++ b/apps/app/layouts/Navbar/Sidebar.tsx @@ -9,6 +9,7 @@ import authenticationService from "lib/services/authentication.service"; // hooks import useUser from "lib/hooks/useUser"; import useTheme from "lib/hooks/useTheme"; +import useToast from "lib/hooks/useToast"; // components import CreateProjectModal from "components/project/CreateProjectModal"; // headless ui @@ -119,6 +120,8 @@ const Sidebar: React.FC = () => { const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme(); + const { setToastAlert } = useToast(); + return (