Merge pull request #1311 from makeplane/stage-release

promote: staging to master
This commit is contained in:
Vamsi Kurama 2023-06-16 21:34:03 +05:30 committed by GitHub
commit dbbce439ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 421 additions and 338 deletions

View File

@ -195,6 +195,11 @@ class InviteWorkspaceEndpoint(BaseAPIView):
{"error": "Emails are required"}, status=status.HTTP_400_BAD_REQUEST
)
# check for role level
requesting_user = WorkspaceMember.objects.get(workspace__slug=slug, member=request.user)
if len([email for email in emails if int(email.get("role", 10)) > requesting_user.role]):
return Response({"error": "You cannot invite a user with higher role"}, status=status.HTTP_400_BAD_REQUEST)
workspace = Workspace.objects.get(slug=slug)
# Check if user is already a member of workspace

View File

@ -9,11 +9,13 @@ import { useForm, Controller } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react";
// ui
import { CustomSelect, PrimaryButton, SecondaryButton, TextArea } from "components/ui";
// hooks
import useToast from "hooks/use-toast";
// services
import projectService from "services/project.service";
import workspaceService from "services/workspace.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// hooks
import useToast from "hooks/use-toast";
// types
import { ICurrentUserResponse, IProjectMemberInvitation } from "types";
// fetch-keys
@ -46,6 +48,7 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
const { workspaceSlug, projectId } = router.query;
const { setToastAlert } = useToast();
const { memberDetails } = useProjectMyMembership();
const { data: people } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null,
@ -202,11 +205,15 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
input
width="w-full"
>
{Object.entries(ROLE).map(([key, label]) => (
{Object.entries(ROLE).map(([key, label]) => {
if (parseInt(key) > (memberDetails?.role ?? 5)) return null;
return (
<CustomSelect.Option key={key} value={key}>
{label}
</CustomSelect.Option>
))}
);
})}
</CustomSelect>
)}
/>

View File

@ -1,17 +1,22 @@
import React from "react";
import { mutate } from "swr";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// headless
import { Dialog, Transition } from "@headlessui/react";
// services
import workspaceService from "services/workspace.service";
// ui
import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ui";
// contexts
import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
// hooks
import useToast from "hooks/use-toast";
// ui
import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ui";
// types
import { ICurrentUserResponse, IWorkspaceMemberInvitation } from "types";
// fetch keys
// fetch-keys
import { WORKSPACE_INVITATIONS } from "constants/fetch-keys";
// constants
import { ROLE } from "constants/workspace";
@ -37,6 +42,7 @@ const SendWorkspaceInvitationModal: React.FC<Props> = ({
user,
}) => {
const { setToastAlert } = useToast();
const { memberDetails } = useWorkspaceMyMembership();
const {
control,
@ -145,11 +151,15 @@ const SendWorkspaceInvitationModal: React.FC<Props> = ({
width="w-full"
input
>
{Object.entries(ROLE).map(([key, value]) => (
{Object.entries(ROLE).map(([key, value]) => {
if (parseInt(key) > (memberDetails?.role ?? 5)) return null;
return (
<CustomSelect.Option key={key} value={key}>
{value}
</CustomSelect.Option>
))}
);
})}
</CustomSelect>
)}
/>

View File

@ -0,0 +1,61 @@
import { createContext, useContext } from "react";
// next
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import workspaceService from "services/workspace.service";
// types
import { IWorkspaceMember } from "types";
// fetch-keys
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
type ContextType = {
loading: boolean;
memberDetails?: IWorkspaceMember;
error: any;
};
export const WorkspaceMemberContext = createContext<ContextType>({} as ContextType);
type Props = {
children: React.ReactNode;
};
export const WorkspaceMemberProvider: React.FC<Props> = (props) => {
const { children } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: memberDetails, error } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug.toString()) : null,
workspaceSlug ? () => workspaceService.workspaceMemberMe(workspaceSlug.toString()) : null
);
const loading = !memberDetails && !error;
return (
<WorkspaceMemberContext.Provider value={{ loading, memberDetails, error }}>
{children}
</WorkspaceMemberContext.Provider>
);
};
export const useWorkspaceMyMembership = () => {
const context = useContext(WorkspaceMemberContext);
if (context === undefined)
throw new Error(`useWorkspaceMember must be used within a WorkspaceMemberProvider.`);
return {
...context,
memberRole: {
isOwner: context.memberDetails?.role === 20,
isMember: context.memberDetails?.role === 15,
isViewer: context.memberDetails?.role === 10,
isGuest: context.memberDetails?.role === 5,
},
};
};

View File

@ -7,6 +7,8 @@ import useSWR from "swr";
// services
import workspaceServices from "services/workspace.service";
// contexts
import { WorkspaceMemberProvider } from "contexts/workspace-member.context";
// layouts
import AppSidebar from "layouts/app-layout/app-sidebar";
import AppHeader from "layouts/app-layout/app-header";
@ -78,6 +80,7 @@ export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
return (
<UserAuthorizationLayout>
<WorkspaceMemberProvider>
<CommandPalette />
<div className="relative flex h-screen w-full overflow-hidden">
<AppSidebar toggleSidebar={toggleSidebar} setToggleSidebar={setToggleSidebar} />
@ -120,6 +123,7 @@ export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
</main>
)}
</div>
</WorkspaceMemberProvider>
</UserAuthorizationLayout>
);
};

View File

@ -89,7 +89,17 @@ const MembersSettings: NextPage = () => {
const currentUser = projectMembers?.find((item) => item.member.id === user?.id);
return (
<>
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${projectDetails?.name ?? "Project"}`}
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
/>
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
>
<ConfirmProjectMemberRemove
isOpen={Boolean(selectedRemoveMember) || Boolean(selectedInviteRemoveMember)}
onClose={() => {
@ -136,17 +146,6 @@ const MembersSettings: NextPage = () => {
members={members}
user={user}
/>
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${projectDetails?.name ?? "Project"}`}
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
/>
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
>
<div className="p-8">
<SettingsHeader />
<section className="space-y-5">
@ -278,7 +277,6 @@ const MembersSettings: NextPage = () => {
</section>
</div>
</ProjectAuthorizationWrapper>
</>
);
};

View File

@ -85,7 +85,17 @@ const MembersSettings: NextPage = () => {
const currentUser = workspaceMembers?.find((item) => item.member?.id === user?.id);
return (
<>
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${activeWorkspace?.name ?? "Workspace"}`}
link={`/${workspaceSlug}`}
/>
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
>
<ConfirmWorkspaceMemberRemove
isOpen={Boolean(selectedRemoveMember) || Boolean(selectedInviteRemoveMember)}
onClose={() => {
@ -137,17 +147,6 @@ const MembersSettings: NextPage = () => {
members={members}
user={user}
/>
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${activeWorkspace?.name ?? "Workspace"}`}
link={`/${workspaceSlug}`}
/>
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
>
<div className="p-8">
<SettingsHeader />
<section className="space-y-5">
@ -279,7 +278,6 @@ const MembersSettings: NextPage = () => {
</section>
</div>
</WorkspaceAuthorizationLayout>
</>
);
};