chore: user cannot update their own role (#1041)

This commit is contained in:
Aaryan Khandelwal 2023-05-15 11:35:19 +05:30 committed by GitHub
parent 290318603d
commit 9fdc56db0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 55 additions and 39 deletions

View File

@ -5,6 +5,7 @@ import Image from "next/image";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
@ -12,6 +13,8 @@ import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
import projectService from "services/project.service"; import projectService from "services/project.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components
import { SettingsHeader } from "components/project";
// ui // ui
import { CustomSelect, Loader, SecondaryButton } from "components/ui"; import { CustomSelect, Loader, SecondaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
@ -20,7 +23,6 @@ import { IProject, IWorkspace } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys"; import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys";
import { SettingsHeader } from "components/project";
const defaultValues: Partial<IProject> = { const defaultValues: Partial<IProject> = {
project_lead: null, project_lead: null,
@ -104,7 +106,7 @@ const ControlSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<form onSubmit={handleSubmit(onSubmit)} className="px-24 py-8"> <form onSubmit={handleSubmit(onSubmit)} className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<div className="space-y-8 sm:space-y-12"> <div className="space-y-8 sm:space-y-12">
<div className="grid grid-cols-12 items-start gap-4 sm:gap-16"> <div className="grid grid-cols-12 items-start gap-4 sm:gap-16">

View File

@ -13,6 +13,7 @@ import useProjectDetails from "hooks/use-project-details";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates"; import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
import { SettingsHeader } from "components/project";
//hooks //hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
@ -27,7 +28,6 @@ import { IEstimate, IProject } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
import { SettingsHeader } from "components/project";
const EstimatesSettings: NextPage = () => { const EstimatesSettings: NextPage = () => {
const [estimateFormOpen, setEstimateFormOpen] = useState(false); const [estimateFormOpen, setEstimateFormOpen] = useState(false);
@ -118,7 +118,7 @@ const EstimatesSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<section className="flex items-center justify-between"> <section className="flex items-center justify-between">
<h3 className="text-2xl font-semibold">Estimates</h3> <h3 className="text-2xl font-semibold">Estimates</h3>

View File

@ -11,6 +11,8 @@ import trackEventServices, { MiscellaneousEventType } from "services/track-event
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components
import { SettingsHeader } from "components/project";
// ui // ui
import { SecondaryButton, ToggleSwitch } from "components/ui"; import { SecondaryButton, ToggleSwitch } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
@ -22,7 +24,6 @@ import { IFavoriteProject, IProject } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
import { SettingsHeader } from "components/project";
const featuresList = [ const featuresList = [
{ {
@ -163,7 +164,7 @@ const FeaturesSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<section className="space-y-8"> <section className="space-y-8">
<h3 className="text-2xl font-semibold">Features</h3> <h3 className="text-2xl font-semibold">Features</h3>

View File

@ -151,7 +151,7 @@ const GeneralSettings: NextPage = () => {
router.push(`/${workspaceSlug}/projects`); router.push(`/${workspaceSlug}/projects`);
}} }}
/> />
<form onSubmit={handleSubmit(onSubmit)} className="py-8 px-24"> <form onSubmit={handleSubmit(onSubmit)} className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<div className="space-y-8 sm:space-y-12"> <div className="space-y-8 sm:space-y-12">
<div className="grid grid-cols-12 items-start gap-4 sm:gap-16"> <div className="grid grid-cols-12 items-start gap-4 sm:gap-16">

View File

@ -54,7 +54,7 @@ const ProjectIntegrations: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
{workspaceIntegrations ? ( {workspaceIntegrations ? (
workspaceIntegrations.length > 0 ? ( workspaceIntegrations.length > 0 ? (

View File

@ -16,6 +16,7 @@ import {
SingleLabel, SingleLabel,
SingleLabelGroup, SingleLabelGroup,
} from "components/labels"; } from "components/labels";
import { SettingsHeader } from "components/project";
// ui // ui
import { Loader, PrimaryButton } from "components/ui"; import { Loader, PrimaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
@ -26,7 +27,6 @@ import { IIssueLabels } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
import { SettingsHeader } from "components/project";
const LabelsSettings: NextPage = () => { const LabelsSettings: NextPage = () => {
// create/edit label form // create/edit label form
@ -102,7 +102,7 @@ const LabelsSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<section className="grid grid-cols-12 gap-10"> <section className="grid grid-cols-12 gap-10">
<div className="col-span-12 sm:col-span-5"> <div className="col-span-12 sm:col-span-5">

View File

@ -11,11 +11,13 @@ import workspaceService from "services/workspace.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useProjectDetails from "hooks/use-project-details"; import useProjectDetails from "hooks/use-project-details";
import useUser from "hooks/use-user";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove"; import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
import SendProjectInvitationModal from "components/project/send-project-invitation-modal"; import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
import { SettingsHeader } from "components/project";
// ui // ui
import { CustomMenu, CustomSelect, Loader } from "components/ui"; import { CustomMenu, CustomSelect, Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
@ -27,7 +29,6 @@ import type { NextPage } from "next";
import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys"; import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys";
// constants // constants
import { ROLE } from "constants/workspace"; import { ROLE } from "constants/workspace";
import { SettingsHeader } from "components/project";
const MembersSettings: NextPage = () => { const MembersSettings: NextPage = () => {
const [inviteModal, setInviteModal] = useState(false); const [inviteModal, setInviteModal] = useState(false);
@ -36,17 +37,17 @@ const MembersSettings: NextPage = () => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { const router = useRouter();
query: { workspaceSlug, projectId }, const { workspaceSlug, projectId } = router.query;
} = useRouter();
const { user } = useUser();
const { projectDetails } = useProjectDetails();
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null,
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null) () => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
); );
const { projectDetails } = useProjectDetails();
const { data: projectMembers, mutate: mutateMembers } = useSWR( const { data: projectMembers, mutate: mutateMembers } = useSWR(
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId
@ -62,8 +63,9 @@ const MembersSettings: NextPage = () => {
); );
const members = [ const members = [
...(projectMembers?.map((item: any) => ({ ...(projectMembers?.map((item) => ({
id: item.id, id: item.id,
memberId: item.member?.id,
avatar: item.member?.avatar, avatar: item.member?.avatar,
first_name: item.member?.first_name, first_name: item.member?.first_name,
last_name: item.member?.last_name, last_name: item.member?.last_name,
@ -74,6 +76,7 @@ const MembersSettings: NextPage = () => {
})) || []), })) || []),
...(projectInvitations?.map((item: any) => ({ ...(projectInvitations?.map((item: any) => ({
id: item.id, id: item.id,
memberId: item.id,
avatar: item.avatar ?? "", avatar: item.avatar ?? "",
first_name: item.first_name ?? item.email, first_name: item.first_name ?? item.email,
last_name: item.last_name ?? "", last_name: item.last_name ?? "",
@ -142,7 +145,7 @@ const MembersSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<section className="space-y-8"> <section className="space-y-8">
<div className="flex items-end justify-between gap-4"> <div className="flex items-end justify-between gap-4">
@ -231,6 +234,7 @@ const MembersSettings: NextPage = () => {
}); });
}} }}
position="right" position="right"
disabled={member.memberId === user?.id}
> >
{Object.keys(ROLE).map((key) => ( {Object.keys(ROLE).map((key) => (
<CustomSelect.Option key={key} value={key}> <CustomSelect.Option key={key} value={key}>

View File

@ -17,6 +17,7 @@ import {
SingleState, SingleState,
StateGroup, StateGroup,
} from "components/states"; } from "components/states";
import { SettingsHeader } from "components/project";
// ui // ui
import { Loader } from "components/ui"; import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
@ -28,7 +29,6 @@ import { getStatesList, orderStateGroups } from "helpers/state.helper";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { STATES_LIST } from "constants/fetch-keys"; import { STATES_LIST } from "constants/fetch-keys";
import { SettingsHeader } from "components/project";
const StatesSettings: NextPage = () => { const StatesSettings: NextPage = () => {
const [activeGroup, setActiveGroup] = useState<StateGroup>(null); const [activeGroup, setActiveGroup] = useState<StateGroup>(null);
@ -67,7 +67,7 @@ const StatesSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<div className="grid grid-cols-12 gap-10"> <div className="grid grid-cols-12 gap-10">
<div className="col-span-12 sm:col-span-5"> <div className="col-span-12 sm:col-span-5">

View File

@ -39,7 +39,7 @@ const BillingSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<section className="space-y-8"> <section className="space-y-8">
<div> <div>

View File

@ -23,7 +23,7 @@ const ImportExport: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<IntegrationGuide /> <IntegrationGuide />
</div> </div>

View File

@ -173,7 +173,7 @@ const WorkspaceSettings: NextPage = () => {
}} }}
data={activeWorkspace ?? null} data={activeWorkspace ?? null}
/> />
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
{activeWorkspace ? ( {activeWorkspace ? (
<div className="space-y-8 sm:space-y-12"> <div className="space-y-8 sm:space-y-12">

View File

@ -47,7 +47,7 @@ const WorkspaceIntegrations: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<section className="space-y-8"> <section className="space-y-8">
<div className="flex flex-col items-start gap-3"> <div className="flex flex-col items-start gap-3">

View File

@ -5,10 +5,11 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// hooks
import useToast from "hooks/use-toast";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// hooks
import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
import { SettingsHeader } from "components/workspace"; import { SettingsHeader } from "components/workspace";
@ -37,24 +38,27 @@ const MembersSettings: NextPage = () => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { user } = useUser();
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug.toString()) : null,
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null) () => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug.toString()) : null)
); );
const { data: workspaceMembers, mutate: mutateMembers } = useSWR<any[]>( const { data: workspaceMembers, mutate: mutateMembers } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null, workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug.toString()) : null,
workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug.toString()) : null
); );
const { data: workspaceInvitations, mutate: mutateInvitations } = useSWR<any[]>( const { data: workspaceInvitations, mutate: mutateInvitations } = useSWR(
workspaceSlug ? WORKSPACE_INVITATIONS : null, workspaceSlug ? WORKSPACE_INVITATIONS : null,
workspaceSlug ? () => workspaceService.workspaceInvitations(workspaceSlug as string) : null workspaceSlug ? () => workspaceService.workspaceInvitations(workspaceSlug.toString()) : null
); );
const members = [ const members = [
...(workspaceMembers?.map((item) => ({ ...(workspaceMembers?.map((item) => ({
id: item.id, id: item.id,
memberId: item.member?.id,
avatar: item.member?.avatar, avatar: item.member?.avatar,
first_name: item.member?.first_name, first_name: item.member?.first_name,
last_name: item.member?.last_name, last_name: item.member?.last_name,
@ -65,9 +69,10 @@ const MembersSettings: NextPage = () => {
})) || []), })) || []),
...(workspaceInvitations?.map((item) => ({ ...(workspaceInvitations?.map((item) => ({
id: item.id, id: item.id,
avatar: item.avatar ?? "", memberId: item.id,
first_name: item.first_name ?? item.email, avatar: "",
last_name: item.last_name ?? "", first_name: item.email,
last_name: "",
email: item.email, email: item.email,
role: item.role, role: item.role,
status: item.accepted, status: item.accepted,
@ -138,7 +143,7 @@ const MembersSettings: NextPage = () => {
</Breadcrumbs> </Breadcrumbs>
} }
> >
<div className="px-24 py-8"> <div className="p-8 lg:px-24">
<SettingsHeader /> <SettingsHeader />
<section className="space-y-8"> <section className="space-y-8">
<div className="flex items-end justify-between gap-4"> <div className="flex items-end justify-between gap-4">
@ -197,8 +202,10 @@ const MembersSettings: NextPage = () => {
label={ROLE[member.role as keyof typeof ROLE]} label={ROLE[member.role as keyof typeof ROLE]}
value={member.role} value={member.role}
onChange={(value: any) => { onChange={(value: any) => {
if (!workspaceSlug) return;
workspaceService workspaceService
.updateWorkspaceMember(activeWorkspace?.slug as string, member.id, { .updateWorkspaceMember(workspaceSlug?.toString(), member.id, {
role: value, role: value,
}) })
.then(() => { .then(() => {
@ -224,6 +231,7 @@ const MembersSettings: NextPage = () => {
}); });
}} }}
position="right" position="right"
disabled={member.memberId === user?.id}
> >
{Object.keys(ROLE).map((key) => ( {Object.keys(ROLE).map((key) => (
<CustomSelect.Option key={key} value={key}> <CustomSelect.Option key={key} value={key}>

View File

@ -60,6 +60,7 @@ type ProjectViewTheme = {
}; };
export interface IProjectMember { export interface IProjectMember {
id: string;
member: IUserLite; member: IUserLite;
project: IProject; project: IProject;
workspace: IWorkspace; workspace: IWorkspace;

View File

@ -23,9 +23,9 @@ export interface IWorkspaceLite {
} }
export interface IWorkspaceMemberInvitation { export interface IWorkspaceMemberInvitation {
accepted: boolean;
readonly id: string; readonly id: string;
email: string; email: string;
accepted: boolean;
token: string; token: string;
message: string; message: string;
responded_at: Date; responded_at: Date;