fix: new auth layer (#740)

* chore: made workspace authorization wrapper component

* chore: added todos

* chore: workspace pages new layout

* chore: project authorization wrapper

* chore: new project authorization wrapper

* fix: authorization for member roles

* chore: new auth screens ui

---------

Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com>
This commit is contained in:
Aaryan Khandelwal 2023-04-08 13:46:46 +05:30 committed by GitHub
parent beedd57ee1
commit 3947a86fa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 997 additions and 1284 deletions

View File

@ -0,0 +1,3 @@
export * from "./project";
export * from "./workspace";
export * from "./not-authorized-view";

View File

@ -7,16 +7,16 @@ import { useRouter } from "next/router";
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
// hooks // hooks
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
// img // images
import ProjectSettingImg from "public/project-setting.svg"; import ProjectNotAuthorizedImg from "public/auth/project-not-authorized.svg";
import WorkspaceNotAuthorizedImg from "public/auth/workspace-not-authorized.svg";
type TNotAuthorizedViewProps = { type Props = {
actionButton?: React.ReactNode; actionButton?: React.ReactNode;
type: "project" | "workspace";
}; };
export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => { export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
const { actionButton } = props;
const { user } = useUser(); const { user } = useUser();
const { asPath: currentPath } = useRouter(); const { asPath: currentPath } = useRouter();
@ -29,7 +29,12 @@ export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => {
> >
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center"> <div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
<div className="h-44 w-72"> <div className="h-44 w-72">
<Image src={ProjectSettingImg} height="176" width="288" alt="ProjectSettingImg" /> <Image
src={type === "project" ? ProjectNotAuthorizedImg : WorkspaceNotAuthorizedImg}
height="176"
width="288"
alt="ProjectSettingImg"
/>
</div> </div>
<h1 className="text-xl font-medium text-gray-900"> <h1 className="text-xl font-medium text-gray-900">
Oops! You are not authorized to view this page Oops! You are not authorized to view this page
@ -38,7 +43,7 @@ export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => {
<div className="w-full text-base text-gray-500 max-w-md "> <div className="w-full text-base text-gray-500 max-w-md ">
{user ? ( {user ? (
<p className=""> <p className="">
You have signed in as {user.email}.{" "} You have signed in as {user.email}. <br />
<Link href={`/signin?next=${currentPath}`}> <Link href={`/signin?next=${currentPath}`}>
<a className="text-gray-900 font-medium">Sign in</a> <a className="text-gray-900 font-medium">Sign in</a>
</Link>{" "} </Link>{" "}

View File

@ -0,0 +1 @@
export * from "./join-project";

View File

@ -0,0 +1,64 @@
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { mutate } from "swr";
// ui
import { PrimaryButton } from "components/ui";
// icon
import { AssignmentClipboardIcon } from "components/icons";
// img
import JoinProjectImg from "public/auth/project-not-authorized.svg";
import projectService from "services/project.service";
// fetch-keys
import { PROJECT_MEMBERS } from "constants/fetch-keys";
export const JoinProject: React.FC = () => {
const [isJoiningProject, setIsJoiningProject] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const handleJoin = () => {
setIsJoiningProject(true);
projectService
.joinProject(workspaceSlug as string, {
project_ids: [projectId as string],
})
.then(() => {
setIsJoiningProject(false);
mutate(PROJECT_MEMBERS(projectId as string));
})
.catch((err) => {
console.error(err);
});
};
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
<div className="h-44 w-72">
<Image src={JoinProjectImg} height="176" width="288" alt="JoinProject" />
</div>
<h1 className="text-xl font-medium text-gray-900">You are not a member of this project</h1>
<div className="w-full max-w-md text-base text-gray-500 ">
<p className="mx-auto w-full text-sm md:w-3/4">
You are not a member of this project, but you can join this project by clicking the button
below.
</p>
</div>
<div>
<PrimaryButton
className="flex items-center gap-1"
loading={isJoiningProject}
onClick={handleJoin}
>
<AssignmentClipboardIcon height={16} width={16} color="white" />
{isJoiningProject ? "Joining..." : "Click to join"}
</PrimaryButton>
</div>
</div>
);
};

View File

@ -0,0 +1 @@
export * from "./not-a-member";

View File

@ -0,0 +1,46 @@
import Link from "next/link";
import { useRouter } from "next/router";
// layouts
import DefaultLayout from "layouts/default-layout";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
export const NotAWorkspaceMember = () => {
const router = useRouter();
return (
<DefaultLayout
meta={{
title: "Plane - Unauthorized User",
description: "Unauthorized user",
}}
>
<div className="grid h-full place-items-center p-4">
<div className="space-y-8 text-center">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Not Authorized!</h3>
<p className="text-sm text-gray-500 w-1/2 mx-auto">
You{"'"}re not a member of this workspace. Please contact the workspace admin to get
an invitation or check your pending invitations.
</p>
</div>
<div className="flex items-center gap-2 justify-center">
<Link href="/invitations">
<a>
<SecondaryButton onClick={() => router.back()}>
Check pending invites
</SecondaryButton>
</a>
</Link>
<Link href="/create-workspace">
<a>
<PrimaryButton>Create new workspace</PrimaryButton>
</a>
</Link>
</div>
</div>
</div>
</DefaultLayout>
);
};

View File

@ -8,7 +8,6 @@ export * from "./image-upload-modal";
export * from "./issues-view-filter"; export * from "./issues-view-filter";
export * from "./issues-view"; export * from "./issues-view";
export * from "./link-modal"; export * from "./link-modal";
export * from "./not-authorized-view";
export * from "./image-picker-popover"; export * from "./image-picker-popover";
export * from "./filter-list"; export * from "./filter-list";
export * from "./feeds"; export * from "./feeds";

View File

@ -11,6 +11,8 @@ import issuesService from "services/issues.service";
import stateService from "services/state.service"; import stateService from "services/state.service";
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
import trackEventServices from "services/track-event.service"; import trackEventServices from "services/track-event.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view"; import useIssuesView from "hooks/use-issues-view";
@ -49,14 +51,12 @@ type Props = {
type?: "issue" | "cycle" | "module"; type?: "issue" | "cycle" | "module";
openIssuesListModal?: () => void; openIssuesListModal?: () => void;
isCompleted?: boolean; isCompleted?: boolean;
userAuth: UserAuth;
}; };
export const IssuesView: React.FC<Props> = ({ export const IssuesView: React.FC<Props> = ({
type = "issue", type = "issue",
openIssuesListModal, openIssuesListModal,
isCompleted = false, isCompleted = false,
userAuth,
}) => { }) => {
// create issue modal // create issue modal
const [createIssueModal, setCreateIssueModal] = useState(false); const [createIssueModal, setCreateIssueModal] = useState(false);
@ -84,6 +84,8 @@ export const IssuesView: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { const {
@ -494,7 +496,7 @@ export const IssuesView: React.FC<Props> = ({
: null : null
} }
isCompleted={isCompleted} isCompleted={isCompleted}
userAuth={userAuth} userAuth={memberRole}
/> />
) : issueView === "kanban" ? ( ) : issueView === "kanban" ? (
<AllBoards <AllBoards
@ -514,7 +516,7 @@ export const IssuesView: React.FC<Props> = ({
: null : null
} }
isCompleted={isCompleted} isCompleted={isCompleted}
userAuth={userAuth} userAuth={memberRole}
/> />
) : ( ) : (
<CalendarView /> <CalendarView />

View File

@ -4,6 +4,8 @@ import dynamic from "next/dynamic";
// react-hook-form // react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// components // components
import { Loader, TextArea } from "components/ui"; import { Loader, TextArea } from "components/ui";
const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), {
@ -15,7 +17,7 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor
), ),
}); });
// types // types
import { IIssue, UserAuth } from "types"; import { IIssue } from "types";
export interface IssueDescriptionFormValues { export interface IssueDescriptionFormValues {
name: string; name: string;
@ -26,17 +28,14 @@ export interface IssueDescriptionFormValues {
export interface IssueDetailsProps { export interface IssueDetailsProps {
issue: IIssue; issue: IIssue;
handleFormSubmit: (value: IssueDescriptionFormValues) => Promise<void>; handleFormSubmit: (value: IssueDescriptionFormValues) => Promise<void>;
userAuth: UserAuth;
} }
export const IssueDescriptionForm: FC<IssueDetailsProps> = ({ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({ issue, handleFormSubmit }) => {
issue,
handleFormSubmit,
userAuth,
}) => {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [characterLimit, setCharacterLimit] = useState(false); const [characterLimit, setCharacterLimit] = useState(false);
const { memberRole } = useProjectMyMembership();
const { const {
handleSubmit, handleSubmit,
watch, watch,
@ -86,7 +85,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
reset(issue); reset(issue);
}, [issue, reset]); }, [issue, reset]);
const isNotAllowed = userAuth.isGuest || userAuth.isViewer; const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
return ( return (
<div className="relative"> <div className="relative">

View File

@ -15,6 +15,8 @@ import useToast from "hooks/use-toast";
// services // services
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// components // components
import { LinkModal, LinksList } from "components/core"; import { LinkModal, LinksList } from "components/core";
import { import {
@ -45,7 +47,7 @@ import {
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// types // types
import type { ICycle, IIssue, IIssueLabels, IIssueLink, IModule, UserAuth } from "types"; import type { ICycle, IIssue, IIssueLabels, IIssueLink, IModule } from "types";
// fetch-keys // fetch-keys
import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys"; import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
@ -54,7 +56,6 @@ type Props = {
submitChanges: (formData: Partial<IIssue>) => void; submitChanges: (formData: Partial<IIssue>) => void;
issueDetail: IIssue | undefined; issueDetail: IIssue | undefined;
watch: UseFormWatch<IIssue>; watch: UseFormWatch<IIssue>;
userAuth: UserAuth;
}; };
const defaultValues: Partial<IIssueLabels> = { const defaultValues: Partial<IIssueLabels> = {
@ -67,7 +68,6 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
submitChanges, submitChanges,
issueDetail, issueDetail,
watch: watchIssue, watch: watchIssue,
userAuth,
}) => { }) => {
const [createLabelForm, setCreateLabelForm] = useState(false); const [createLabelForm, setCreateLabelForm] = useState(false);
const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false);
@ -76,6 +76,8 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query; const { workspaceSlug, projectId, issueId } = router.query;
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { data: issues } = useSWR( const { data: issues } = useSWR(
@ -213,7 +215,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
reset(); reset();
}, [createLabelForm, reset]); }, [createLabelForm, reset]);
const isNotAllowed = userAuth.isGuest || userAuth.isViewer; const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
return ( return (
<> <>
@ -260,7 +262,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarStateSelect <SidebarStateSelect
value={value} value={value}
onChange={(val: string) => submitChanges({ state: val })} onChange={(val: string) => submitChanges({ state: val })}
userAuth={userAuth} userAuth={memberRole}
/> />
)} )}
/> />
@ -271,7 +273,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarAssigneeSelect <SidebarAssigneeSelect
value={value} value={value}
onChange={(val: string[]) => submitChanges({ assignees_list: val })} onChange={(val: string[]) => submitChanges({ assignees_list: val })}
userAuth={userAuth} userAuth={memberRole}
/> />
)} )}
/> />
@ -282,7 +284,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarPrioritySelect <SidebarPrioritySelect
value={value} value={value}
onChange={(val: string) => submitChanges({ priority: val })} onChange={(val: string) => submitChanges({ priority: val })}
userAuth={userAuth} userAuth={memberRole}
/> />
)} )}
/> />
@ -293,7 +295,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarEstimateSelect <SidebarEstimateSelect
value={value} value={value}
onChange={(val: number) => submitChanges({ estimate_point: val })} onChange={(val: number) => submitChanges({ estimate_point: val })}
userAuth={userAuth} userAuth={memberRole}
/> />
)} )}
/> />
@ -327,19 +329,19 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
) )
} }
watch={watchIssue} watch={watchIssue}
userAuth={userAuth} userAuth={memberRole}
/> />
<SidebarBlockerSelect <SidebarBlockerSelect
submitChanges={submitChanges} submitChanges={submitChanges}
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []} issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
watch={watchIssue} watch={watchIssue}
userAuth={userAuth} userAuth={memberRole}
/> />
<SidebarBlockedSelect <SidebarBlockedSelect
submitChanges={submitChanges} submitChanges={submitChanges}
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []} issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
watch={watchIssue} watch={watchIssue}
userAuth={userAuth} userAuth={memberRole}
/> />
<div className="flex flex-wrap items-center py-2"> <div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2"> <div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
@ -369,12 +371,12 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarCycleSelect <SidebarCycleSelect
issueDetail={issueDetail} issueDetail={issueDetail}
handleCycleChange={handleCycleChange} handleCycleChange={handleCycleChange}
userAuth={userAuth} userAuth={memberRole}
/> />
<SidebarModuleSelect <SidebarModuleSelect
issueDetail={issueDetail} issueDetail={issueDetail}
handleModuleChange={handleModuleChange} handleModuleChange={handleModuleChange}
userAuth={userAuth} userAuth={memberRole}
/> />
</div> </div>
</div> </div>
@ -637,7 +639,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<LinksList <LinksList
links={issueDetail.issue_link} links={issueDetail.issue_link}
handleDeleteLink={handleDeleteLink} handleDeleteLink={handleDeleteLink}
userAuth={userAuth} userAuth={memberRole}
/> />
) : null} ) : null}
</div> </div>

View File

@ -9,6 +9,8 @@ import useSWR, { mutate } from "swr";
import { Disclosure, Transition } from "@headlessui/react"; import { Disclosure, Transition } from "@headlessui/react";
// services // services
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// components // components
import { ExistingIssuesListModal } from "components/core"; import { ExistingIssuesListModal } from "components/core";
import { CreateUpdateIssueModal } from "components/issues"; import { CreateUpdateIssueModal } from "components/issues";
@ -19,16 +21,15 @@ import { ChevronRightIcon, PlusIcon, XMarkIcon } from "@heroicons/react/24/outli
// helpers // helpers
import { orderArrayBy } from "helpers/array.helper"; import { orderArrayBy } from "helpers/array.helper";
// types // types
import { IIssue, UserAuth } from "types"; import { IIssue } from "types";
// fetch-keys // fetch-keys
import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys"; import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys";
type Props = { type Props = {
parentIssue: IIssue; parentIssue: IIssue;
userAuth: UserAuth;
}; };
export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => { export const SubIssuesList: FC<Props> = ({ parentIssue }) => {
// states // states
const [createIssueModal, setCreateIssueModal] = useState(false); const [createIssueModal, setCreateIssueModal] = useState(false);
const [subIssuesListModal, setSubIssuesListModal] = useState(false); const [subIssuesListModal, setSubIssuesListModal] = useState(false);
@ -37,6 +38,8 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query; const { workspaceSlug, projectId, issueId } = router.query;
const { memberRole } = useProjectMyMembership();
const { data: subIssues } = useSWR<IIssue[] | undefined>( const { data: subIssues } = useSWR<IIssue[] | undefined>(
workspaceSlug && projectId && issueId ? SUB_ISSUES(issueId as string) : null, workspaceSlug && projectId && issueId ? SUB_ISSUES(issueId as string) : null,
workspaceSlug && projectId && issueId workspaceSlug && projectId && issueId
@ -143,7 +146,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
}); });
}; };
const isNotAllowed = userAuth.isGuest || userAuth.isViewer; const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
return ( return (
<> <>

View File

@ -21,6 +21,8 @@ import { Disclosure, Popover, Transition } from "@headlessui/react";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
// services // services
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
// contexts
import { useProjectMyMembership } from "contexts/project-member.context";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
@ -30,17 +32,16 @@ import ProgressChart from "components/core/sidebar/progress-chart";
import { CustomMenu, CustomSelect, Loader, ProgressBar } from "components/ui"; import { CustomMenu, CustomSelect, Loader, ProgressBar } from "components/ui";
// icon // icon
import { ExclamationIcon } from "components/icons"; import { ExclamationIcon } from "components/icons";
import { LinkIcon } from "@heroicons/react/20/solid";
// helpers // helpers
import { isDateRangeValid, renderDateFormat, renderShortDate } from "helpers/date-time.helper"; import { renderDateFormat, renderShortDate } from "helpers/date-time.helper";
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper"; import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
import { groupBy } from "helpers/array.helper";
// types // types
import { IIssue, IModule, ModuleLink, UserAuth } from "types"; import { IIssue, IModule, ModuleLink } from "types";
// fetch-keys // fetch-keys
import { MODULE_DETAILS } from "constants/fetch-keys"; import { MODULE_DETAILS } from "constants/fetch-keys";
// constant // constant
import { MODULE_STATUS } from "constants/module"; import { MODULE_STATUS } from "constants/module";
import { LinkIcon } from "@heroicons/react/20/solid";
const defaultValues: Partial<IModule> = { const defaultValues: Partial<IModule> = {
lead: "", lead: "",
@ -55,22 +56,17 @@ type Props = {
module?: IModule; module?: IModule;
isOpen: boolean; isOpen: boolean;
moduleIssues?: IIssue[]; moduleIssues?: IIssue[];
userAuth: UserAuth;
}; };
export const ModuleDetailsSidebar: React.FC<Props> = ({ export const ModuleDetailsSidebar: React.FC<Props> = ({ issues, module, isOpen, moduleIssues }) => {
issues,
module,
isOpen,
moduleIssues,
userAuth,
}) => {
const [moduleDeleteModal, setModuleDeleteModal] = useState(false); const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
const [moduleLinkModal, setModuleLinkModal] = useState(false); const [moduleLinkModal, setModuleLinkModal] = useState(false);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { reset, watch, control } = useForm({ const { reset, watch, control } = useForm({
@ -516,7 +512,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
completed: module.completed_issues, completed: module.completed_issues,
cancelled: module.cancelled_issues, cancelled: module.cancelled_issues,
}} }}
userAuth={userAuth} userAuth={memberRole}
module={module} module={module}
/> />
</div> </div>
@ -542,11 +538,11 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
</button> </button>
</div> </div>
<div className="mt-2 space-y-2 hover:bg-gray-100"> <div className="mt-2 space-y-2 hover:bg-gray-100">
{userAuth && module.link_module && module.link_module.length > 0 ? ( {memberRole && module.link_module && module.link_module.length > 0 ? (
<LinksList <LinksList
links={module.link_module} links={module.link_module}
handleDeleteLink={handleDeleteLink} handleDeleteLink={handleDeleteLink}
userAuth={userAuth} userAuth={memberRole}
/> />
) : null} ) : null}
</div> </div>

View File

@ -147,7 +147,6 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
}); });
}; };
// FIXME: remove this and authorize using getServerSideProps
if (myWorkspaceMembership && isOpen) { if (myWorkspaceMembership && isOpen) {
if (myWorkspaceMembership.role <= 10) return <IsGuestCondition setIsOpen={setIsOpen} />; if (myWorkspaceMembership.role <= 10) return <IsGuestCondition setIsOpen={setIsOpen} />;
} }

View File

@ -1,6 +1,5 @@
export * from "./create-project-modal"; export * from "./create-project-modal";
export * from "./delete-project-modal"; export * from "./delete-project-modal";
export * from "./join-project";
export * from "./sidebar-list"; export * from "./sidebar-list";
export * from "./single-integration-card"; export * from "./single-integration-card";
export * from "./single-project-card"; export * from "./single-project-card";

View File

@ -1,41 +0,0 @@
import { FC } from "react";
// next
import Image from "next/image";
// ui
import { PrimaryButton } from "components/ui";
// icon
import { AssignmentClipboardIcon } from "components/icons";
// img
import JoinProjectImg from "public/join-project.svg";
export interface JoinProjectProps {
isJoiningProject: boolean;
handleJoin: () => void;
}
export const JoinProject: FC<JoinProjectProps> = ({ isJoiningProject, handleJoin }) => (
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
<div className="h-44 w-72">
<Image src={JoinProjectImg} height="176" width="288" alt="JoinProject" />
</div>
<h1 className="text-xl font-medium text-gray-900">You are not a member of this project</h1>
<div className="w-full max-w-md text-base text-gray-500 ">
<p className="mx-auto w-full text-sm md:w-3/4">
You are not a member of this project, but you can join this project by clicking the button
below.
</p>
</div>
<div>
<PrimaryButton
className="flex items-center gap-1"
loading={isJoiningProject}
onClick={handleJoin}
>
<AssignmentClipboardIcon height={16} width={16} color="white" />
{isJoiningProject ? "Joining..." : "Click to join"}
</PrimaryButton>
</div>
</div>
);

View File

@ -0,0 +1,73 @@
import { createContext, useContext } from "react";
// next
import { useRouter } from "next/router";
// swr
import useSWR from "swr";
// services
import projectService from "services/project.service";
// keys
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
// types
import { IProjectMember } from "types";
type ContextType = {
loading: boolean;
memberDetails?: IProjectMember;
error: any;
};
export const ProjectMemberContext = createContext<ContextType>({} as ContextType);
type Props = {
children: React.ReactNode;
};
export const ProjectMemberProvider: React.FC<Props> = (props) => {
const { children } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { data: memberDetails, error } = useSWR(
workspaceSlug && projectId ? USER_PROJECT_VIEW(workspaceSlug.toString()) : null,
workspaceSlug && projectId
? () => projectService.projectMemberMe(workspaceSlug.toString(), projectId.toString())
: null,
{
onErrorRetry(err, _, __, ___, revalidateOpts) {
if (err.status === 401 || err.status === 403) return;
revalidateOpts.retryCount = 5;
},
}
);
const loading = !memberDetails && !error;
return (
<ProjectMemberContext.Provider value={{ loading, memberDetails, error }}>
{children}
</ProjectMemberContext.Provider>
);
};
export const useProjectMyMembership = () => {
const context = useContext(ProjectMemberContext);
if (context === undefined)
throw new Error(`useProjectMember must be used within a ProjectMemberProvider.`);
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

@ -12,9 +12,8 @@ import { PrimaryButton, Spinner } from "components/ui";
// icon // icon
import { LayerDiagonalIcon } from "components/icons"; import { LayerDiagonalIcon } from "components/icons";
// components // components
import { NotAuthorizedView } from "components/core"; import { NotAuthorizedView, JoinProject } from "components/auth-screens";
import { CommandPalette } from "components/command-palette"; import { CommandPalette } from "components/command-palette";
import { JoinProject } from "components/project";
// local components // local components
import Container from "layouts/container"; import Container from "layouts/container";
import AppSidebar from "layouts/app-layout/app-sidebar"; import AppSidebar from "layouts/app-layout/app-sidebar";
@ -61,7 +60,6 @@ const AppLayout: FC<AppLayoutProps> = ({
}) => { }) => {
// states // states
const [toggleSidebar, setToggleSidebar] = useState(false); const [toggleSidebar, setToggleSidebar] = useState(false);
const [isJoiningProject, setIsJoiningProject] = useState(false);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
@ -83,21 +81,6 @@ const AppLayout: FC<AppLayoutProps> = ({
memberType?.isViewer || memberType?.isViewer ||
memberType?.isGuest; memberType?.isGuest;
const handleJoin = () => {
setIsJoiningProject(true);
projectService
.joinProject(workspaceSlug as string, {
project_ids: [projectId as string],
})
.then(() => {
setIsJoiningProject(false);
projectMembersMutate();
})
.catch((err) => {
console.error(err);
});
};
return ( return (
<Container meta={meta}> <Container meta={meta}>
<CommandPalette /> <CommandPalette />
@ -123,6 +106,7 @@ const AppLayout: FC<AppLayoutProps> = ({
) )
) )
} }
type="project"
/> />
) : ( ) : (
<main className="flex h-screen w-full min-w-0 flex-col overflow-y-auto"> <main className="flex h-screen w-full min-w-0 flex-col overflow-y-auto">
@ -170,7 +154,7 @@ const AppLayout: FC<AppLayoutProps> = ({
{children} {children}
</div> </div>
) : ( ) : (
<JoinProject isJoiningProject={isJoiningProject} handleJoin={handleJoin} /> <JoinProject />
)} )}
</main> </main>
)} )}

View File

@ -0,0 +1,2 @@
export * from "./project-authorization-wrapper";
export * from "./workspace-authorization-wrapper";

View File

@ -0,0 +1,141 @@
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
// contexts
import { useProjectMyMembership, ProjectMemberProvider } from "contexts/project-member.context";
// hooks
import useUser from "hooks/use-user";
// layouts
import Container from "layouts/container";
import AppHeader from "layouts/app-layout/app-header";
import AppSidebar from "layouts/app-layout/app-sidebar";
import SettingsNavbar from "layouts/settings-navbar";
// components
import { NotAuthorizedView, JoinProject } from "components/auth-screens";
import { CommandPalette } from "components/command-palette";
// ui
import { PrimaryButton } from "components/ui";
// icons
import { LayerDiagonalIcon } from "components/icons";
type Meta = {
title?: string | null;
description?: string | null;
image?: string | null;
url?: string | null;
};
type Props = {
meta?: Meta;
children: React.ReactNode;
noPadding?: boolean;
noHeader?: boolean;
bg?: "primary" | "secondary";
breadcrumbs?: JSX.Element;
left?: JSX.Element;
right?: JSX.Element;
};
export const ProjectAuthorizationWrapper: React.FC<Props> = (props) => (
<ProjectMemberProvider>
<ProjectAuthorizationWrapped {...props} />
</ProjectMemberProvider>
);
const ProjectAuthorizationWrapped: React.FC<Props> = ({
meta,
children,
noPadding = false,
noHeader = false,
bg = "primary",
breadcrumbs,
left,
right,
}) => {
const [toggleSidebar, setToggleSidebar] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const user = useUser();
const { loading, error, memberRole: memberType } = useProjectMyMembership();
const settingsLayout = router.pathname.includes("/settings");
return (
<Container meta={meta}>
<CommandPalette />
<div className="flex h-screen w-full overflow-x-hidden">
<AppSidebar toggleSidebar={toggleSidebar} setToggleSidebar={setToggleSidebar} />
{loading ? (
<div className="container h-screen flex justify-center items-center p-4 text-2xl font-semibold">
<p>Loading...</p>
</div>
) : error?.status === 401 || error?.status === 403 ? (
<JoinProject />
) : error?.status === 404 ? (
<div className="container h-screen grid place-items-center">
<div className="text-center space-y-4">
<p className="text-2xl font-semibold">No such project exist. Create one?</p>
<PrimaryButton
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "p" });
document.dispatchEvent(e);
}}
>
Create project
</PrimaryButton>
</div>
</div>
) : settingsLayout && (memberType?.isGuest || memberType?.isViewer) ? (
<NotAuthorizedView
actionButton={
<Link href={`/${workspaceSlug}/projects/${projectId}/issues`}>
<a>
<PrimaryButton className="flex items-center gap-1">
<LayerDiagonalIcon height={16} width={16} color="white" /> Go to issues
</PrimaryButton>
</a>
</Link>
}
type="project"
/>
) : (
<main className="flex h-screen w-full min-w-0 flex-col overflow-y-auto">
{!noHeader && (
<AppHeader
breadcrumbs={breadcrumbs}
left={left}
right={right}
setToggleSidebar={setToggleSidebar}
/>
)}
<div
className={`flex w-full flex-grow flex-col ${
noPadding ? "" : settingsLayout ? "p-8 lg:px-28" : "p-8"
} ${
bg === "primary" ? "bg-primary" : bg === "secondary" ? "bg-secondary" : "bg-primary"
}`}
>
{settingsLayout && (
<div className="mb-12 space-y-6">
<div>
<h3 className="text-3xl font-semibold">Project Settings</h3>
<p className="mt-1 text-gray-600">
This information will be displayed to every member of the project.
</p>
</div>
<SettingsNavbar />
</div>
)}
{children}
</div>
</main>
)}
</div>
</Container>
);
};

View File

@ -0,0 +1,158 @@
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import workspaceServices from "services/workspace.service";
// hooks
import useUser from "hooks/use-user";
// layouts
import Container from "layouts/container";
import AppSidebar from "layouts/app-layout/app-sidebar";
import AppHeader from "layouts/app-layout/app-header";
import SettingsNavbar from "layouts/settings-navbar";
// components
import { NotAuthorizedView, NotAWorkspaceMember } from "components/auth-screens";
import { CommandPalette } from "components/command-palette";
// icons
import { PrimaryButton } from "components/ui";
import { LayerDiagonalIcon } from "components/icons";
// fetch-keys
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
type Meta = {
title?: string | null;
description?: string | null;
image?: string | null;
url?: string | null;
};
type Props = {
meta?: Meta;
children: React.ReactNode;
noPadding?: boolean;
noHeader?: boolean;
bg?: "primary" | "secondary";
breadcrumbs?: JSX.Element;
left?: JSX.Element;
right?: JSX.Element;
profilePage?: boolean;
};
export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
meta,
children,
noPadding = false,
noHeader = false,
bg = "primary",
breadcrumbs,
left,
right,
profilePage = false,
}) => {
const [toggleSidebar, setToggleSidebar] = useState(false);
const router = useRouter();
const { workspaceSlug } = router.query;
const user = useUser();
const { data: workspaceMemberMe, error } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null,
workspaceSlug ? () => workspaceServices.workspaceMemberMe(workspaceSlug.toString()) : null,
{
onErrorRetry(err, key, config, revalidate, revalidateOpts) {
if (err.status === 401 || err.status === 403) return;
revalidateOpts.retryCount = 5;
},
}
);
if (!workspaceMemberMe && !error)
// TODO: show good loading UI
return (
<div className="container h-screen flex justify-center items-center p-4 text-2xl font-semibold">
<p>Loading...</p>
</div>
);
if (error?.status === 401 || error?.status === 403) return <NotAWorkspaceMember />;
// FIXME: show 404 for workspace not workspace member
if (error?.status === 404) {
return (
<div className="container h-screen flex justify-center items-center">
<p className="text-2xl font-semibold">No such workspace exist. Create one?</p>
</div>
);
}
const settingsLayout = router.pathname.includes("/settings");
const memberType = {
isOwner: workspaceMemberMe?.role === 20,
isMember: workspaceMemberMe?.role === 15,
isViewer: workspaceMemberMe?.role === 10,
isGuest: workspaceMemberMe?.role === 5,
};
return (
<Container meta={meta}>
<CommandPalette />
<div className="flex h-screen w-full overflow-x-hidden">
<AppSidebar toggleSidebar={toggleSidebar} setToggleSidebar={setToggleSidebar} />
{settingsLayout && (memberType?.isGuest || memberType?.isViewer) ? (
<NotAuthorizedView
actionButton={
<Link href={`/${workspaceSlug}`}>
<a>
<PrimaryButton className="flex items-center gap-1">
<LayerDiagonalIcon height={16} width={16} color="white" /> Go to workspace
</PrimaryButton>
</a>
</Link>
}
type="workspace"
/>
) : (
<main className="flex h-screen w-full min-w-0 flex-col overflow-y-auto">
{!noHeader && (
<AppHeader
breadcrumbs={breadcrumbs}
left={left}
right={right}
setToggleSidebar={setToggleSidebar}
/>
)}
<div
className={`flex w-full flex-grow flex-col ${
noPadding ? "" : settingsLayout || profilePage ? "p-8 lg:px-28" : "p-8"
} ${
bg === "primary" ? "bg-primary" : bg === "secondary" ? "bg-secondary" : "bg-primary"
}`}
>
{(settingsLayout || profilePage) && (
<div className="mb-12 space-y-6">
<div>
<h3 className="text-3xl font-semibold">
{profilePage ? "Profile" : "Workspace"} Settings
</h3>
<p className="mt-1 text-gray-600">
{profilePage
? "This information will be visible to only you."
: "This information will be displayed to every member of the workspace."}
</p>
</div>
<SettingsNavbar profilePage={profilePage} />
</div>
)}
{children}
</div>
</main>
)}
</div>
</Container>
);
};

View File

@ -2,7 +2,7 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
type Props = { type Props = {
profilePage: boolean; profilePage?: boolean;
}; };
const SettingsNavbar: React.FC<Props> = ({ profilePage = false }) => { const SettingsNavbar: React.FC<Props> = ({ profilePage = false }) => {

View File

@ -4,10 +4,8 @@ import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// lib
import { requiredAuth } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// services // services
import userService from "services/user.service"; import userService from "services/user.service";
// components // components
@ -18,7 +16,7 @@ import {
IssuesStats, IssuesStats,
} from "components/workspace"; } from "components/workspace";
// types // types
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { USER_WORKSPACE_DASHBOARD } from "constants/fetch-keys"; import { USER_WORKSPACE_DASHBOARD } from "constants/fetch-keys";
@ -38,14 +36,16 @@ const WorkspacePage: NextPage = () => {
}, [month, workspaceSlug]); }, [month, workspaceSlug]);
return ( return (
<AppLayout noHeader={true}> <WorkspaceAuthorizationLayout noHeader>
<div className="h-full w-full"> <div className="h-full w-full">
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
<div <div
className="flex flex-col bg-white justify-between gap-x-2 gap-y-6 rounded-lg px-8 py-6 text-black md:flex-row md:items-center md:py-3" className="flex flex-col bg-white justify-between gap-x-2 gap-y-6 rounded-lg px-8 py-6 text-black md:flex-row md:items-center md:py-3"
// style={{ background: "linear-gradient(90deg, #8e2de2 0%, #4a00e0 100%)" }} // style={{ background: "linear-gradient(90deg, #8e2de2 0%, #4a00e0 100%)" }}
> >
<p className="font-semibold">Plane is a open source application, to support us you can star us on GitHub!</p> <p className="font-semibold">
Plane is a open source application, to support us you can star us on GitHub!
</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* <a href="#" target="_blank" rel="noopener noreferrer"> {/* <a href="#" target="_blank" rel="noopener noreferrer">
View roadmap View roadmap
@ -73,29 +73,8 @@ const WorkspacePage: NextPage = () => {
</div> </div>
</div> </div>
</div> </div>
</AppLayout> </WorkspaceAuthorizationLayout>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
return {
props: {
user,
},
};
};
export default WorkspacePage; export default WorkspacePage;

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// headless ui // headless ui
@ -6,7 +7,7 @@ import { Disclosure, Popover, Transition } from "@headlessui/react";
// icons // icons
import { ChevronDownIcon, PlusIcon, RectangleStackIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon, PlusIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// hooks // hooks
import useIssues from "hooks/use-issues"; import useIssues from "hooks/use-issues";
// ui // ui
@ -36,166 +37,164 @@ const MyIssuesPage: NextPage = () => {
); );
return ( return (
<> <WorkspaceAuthorizationLayout
<AppLayout breadcrumbs={
breadcrumbs={ <Breadcrumbs>
<Breadcrumbs> <BreadcrumbItem title="My Issues" />
<BreadcrumbItem title="My Issues" /> </Breadcrumbs>
</Breadcrumbs> }
} right={
right={ <div className="flex items-center gap-2">
<div className="flex items-center gap-2"> {myIssues && myIssues.length > 0 && (
{myIssues && myIssues.length > 0 && ( <Popover className="relative">
<Popover className="relative"> {({ open }) => (
{({ open }) => ( <>
<> <Popover.Button
<Popover.Button className={`group flex items-center gap-2 rounded-md border bg-transparent p-2 text-xs font-medium hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${
className={`group flex items-center gap-2 rounded-md border bg-transparent p-2 text-xs font-medium hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${ open ? "bg-gray-100 text-gray-900" : "text-gray-500"
open ? "bg-gray-100 text-gray-900" : "text-gray-500" }`}
}`} >
> <span>View</span>
<span>View</span> <ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" /> </Popover.Button>
</Popover.Button>
<Transition <Transition
as={React.Fragment} as={React.Fragment}
enter="transition ease-out duration-200" enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1" enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0" enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150" leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0" leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1" leaveTo="opacity-0 translate-y-1"
> >
<Popover.Panel className="absolute right-1/2 z-10 mr-5 mt-1 w-screen max-w-xs translate-x-1/2 transform overflow-hidden rounded-lg bg-white p-3 shadow-lg"> <Popover.Panel className="absolute right-1/2 z-10 mr-5 mt-1 w-screen max-w-xs translate-x-1/2 transform overflow-hidden rounded-lg bg-white p-3 shadow-lg">
<div className="relative flex flex-col gap-1 gap-y-4"> <div className="relative flex flex-col gap-1 gap-y-4">
<div className="relative flex flex-col gap-1"> <div className="relative flex flex-col gap-1">
<h4 className="text-base text-gray-600">Properties</h4> <h4 className="text-base text-gray-600">Properties</h4>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
{Object.keys(properties).map((key) => ( {Object.keys(properties).map((key) => (
<button <button
key={key} key={key}
type="button" type="button"
className={`rounded border border-theme px-2 py-1 text-xs capitalize ${ className={`rounded border border-theme px-2 py-1 text-xs capitalize ${
properties[key as keyof Properties] properties[key as keyof Properties]
? "border-theme bg-theme text-white" ? "border-theme bg-theme text-white"
: "" : ""
}`} }`}
onClick={() => setProperties(key as keyof Properties)} onClick={() => setProperties(key as keyof Properties)}
> >
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)} {key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
</button> </button>
))} ))}
</div>
</div> </div>
</div> </div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
)}
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
<PlusIcon className="w-4 h-4" />
Add Issue
</PrimaryButton>
</div>
}
>
<div className="flex h-full w-full flex-col space-y-5">
{myIssues ? (
<>
{myIssues.length > 0 ? (
<div className="flex flex-col space-y-5">
<Disclosure as="div" defaultOpen>
{({ open }) => (
<div className="rounded-[10px] border border-gray-300 bg-white">
<div
className={`flex items-center justify-start bg-gray-100 px-5 py-3 ${
open ? "rounded-t-[10px]" : "rounded-[10px]"
}`}
>
<Disclosure.Button>
<div className="flex items-center gap-x-2">
<span>
<ChevronDownIcon
className={`h-4 w-4 text-gray-500 ${
!open ? "-rotate-90 transform" : ""
}`}
/>
</span>
<h2 className="font-medium leading-5">My Issues</h2>
<span className="rounded-full bg-gray-200 py-0.5 px-3 text-sm text-black">
{myIssues.length}
</span>
</div>
</Disclosure.Button>
</div>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
{myIssues.map((issue: IIssue) => (
<MyIssuesListItem
key={issue.id}
issue={issue}
properties={properties}
projectId={issue.project}
/>
))}
</Disclosure.Panel>
</Transition>
</div> </div>
)} </Popover.Panel>
</Disclosure> </Transition>
</div> </>
) : (
<div className="flex h-full w-full flex-col items-center justify-center px-4">
<EmptySpace
title="You don't have any issue assigned to you yet."
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
Icon={RectangleStackIcon}
>
<EmptySpaceItem
title="Create a new issue"
description={
<span>
Use <pre className="inline rounded bg-gray-200 px-2 py-1">C</pre> shortcut
to create a new issue
</span>
}
Icon={PlusIcon}
action={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
/>
</EmptySpace>
</div>
)} )}
</> </Popover>
) : (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
</div>
)} )}
<PrimaryButton
className="flex items-center gap-2"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
}}
>
<PlusIcon className="w-4 h-4" />
Add Issue
</PrimaryButton>
</div> </div>
</AppLayout> }
</> >
<div className="flex h-full w-full flex-col space-y-5">
{myIssues ? (
<>
{myIssues.length > 0 ? (
<div className="flex flex-col space-y-5">
<Disclosure as="div" defaultOpen>
{({ open }) => (
<div className="rounded-[10px] border border-gray-300 bg-white">
<div
className={`flex items-center justify-start bg-gray-100 px-5 py-3 ${
open ? "rounded-t-[10px]" : "rounded-[10px]"
}`}
>
<Disclosure.Button>
<div className="flex items-center gap-x-2">
<span>
<ChevronDownIcon
className={`h-4 w-4 text-gray-500 ${
!open ? "-rotate-90 transform" : ""
}`}
/>
</span>
<h2 className="font-medium leading-5">My Issues</h2>
<span className="rounded-full bg-gray-200 py-0.5 px-3 text-sm text-black">
{myIssues.length}
</span>
</div>
</Disclosure.Button>
</div>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
{myIssues.map((issue: IIssue) => (
<MyIssuesListItem
key={issue.id}
issue={issue}
properties={properties}
projectId={issue.project}
/>
))}
</Disclosure.Panel>
</Transition>
</div>
)}
</Disclosure>
</div>
) : (
<div className="flex h-full w-full flex-col items-center justify-center px-4">
<EmptySpace
title="You don't have any issue assigned to you yet."
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
Icon={RectangleStackIcon}
>
<EmptySpaceItem
title="Create a new issue"
description={
<span>
Use <pre className="inline rounded bg-gray-200 px-2 py-1">C</pre> shortcut
to create a new issue
</span>
}
Icon={PlusIcon}
action={() => {
const e = new KeyboardEvent("keydown", {
key: "c",
});
document.dispatchEvent(e);
}}
/>
</EmptySpace>
</div>
)}
</>
) : (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
</div>
)}
</div>
</WorkspaceAuthorizationLayout>
); );
}; };

View File

@ -1,17 +1,14 @@
import { GetServerSidePropsContext } from "next";
import useSWR from "swr"; import useSWR from "swr";
// services // services
import userService from "services/user.service"; import userService from "services/user.service";
// lib
import { requiredAuth } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import { Feeds } from "components/core";
// ui // ui
import { Loader } from "components/ui"; import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { Feeds } from "components/core";
// fetch-keys // fetch-keys
import { USER_ACTIVITY } from "constants/fetch-keys"; import { USER_ACTIVITY } from "constants/fetch-keys";
@ -19,7 +16,7 @@ const ProfileActivity = () => {
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity()); const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
return ( return (
<AppLayout <WorkspaceAuthorizationLayout
meta={{ meta={{
title: "Plane - My Profile", title: "Plane - My Profile",
}} }}
@ -28,7 +25,6 @@ const ProfileActivity = () => {
<BreadcrumbItem title="My Profile Activity" /> <BreadcrumbItem title="My Profile Activity" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
profilePage profilePage
> >
{userActivity ? ( {userActivity ? (
@ -43,29 +39,8 @@ const ProfileActivity = () => {
<Loader.Item height="40px" /> <Loader.Item height="40px" />
</Loader> </Loader>
)} )}
</AppLayout> </WorkspaceAuthorizationLayout>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
return {
props: {
user,
},
};
};
export default ProfileActivity; export default ProfileActivity;

View File

@ -4,8 +4,6 @@ import Image from "next/image";
// react-hook-form // react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// lib
import { requiredAuth } from "lib/auth";
// services // services
import fileService from "services/file.service"; import fileService from "services/file.service";
import userService from "services/user.service"; import userService from "services/user.service";
@ -13,7 +11,7 @@ import userService from "services/user.service";
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components // components
import { ImageUploadModal } from "components/core"; import { ImageUploadModal } from "components/core";
// ui // ui
@ -22,7 +20,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { UserIcon } from "@heroicons/react/24/outline"; import { UserIcon } from "@heroicons/react/24/outline";
// types // types
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
import type { IUser } from "types"; import type { IUser } from "types";
// constants // constants
import { USER_ROLES } from "constants/workspace"; import { USER_ROLES } from "constants/workspace";
@ -123,7 +121,7 @@ const Profile: NextPage = () => {
}; };
return ( return (
<AppLayout <WorkspaceAuthorizationLayout
meta={{ meta={{
title: "Plane - My Profile", title: "Plane - My Profile",
}} }}
@ -132,7 +130,6 @@ const Profile: NextPage = () => {
<BreadcrumbItem title="My Profile" /> <BreadcrumbItem title="My Profile" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
profilePage profilePage
> >
<ImageUploadModal <ImageUploadModal
@ -282,29 +279,8 @@ const Profile: NextPage = () => {
<Spinner /> <Spinner />
</div> </div>
)} )}
</AppLayout> </WorkspaceAuthorizationLayout>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
return {
props: {
user,
},
};
};
export default Profile; export default Profile;

View File

@ -3,14 +3,11 @@ import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { GetServerSidePropsContext } from "next";
// icons // icons
import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { CyclesIcon } from "components/icons"; import { CyclesIcon } from "components/icons";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// components // components
@ -28,8 +25,6 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
import { getDateRangeStatus } from "helpers/date-time.helper"; import { getDateRangeStatus } from "helpers/date-time.helper";
// types
import { UserAuth } from "types";
// fetch-keys // fetch-keys
import { import {
CYCLE_ISSUES, CYCLE_ISSUES,
@ -39,7 +34,7 @@ import {
PROJECT_ISSUES_LIST, PROJECT_ISSUES_LIST,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
const SingleCycle: React.FC<UserAuth> = (props) => { const SingleCycle: React.FC = () => {
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
const [cycleSidebar, setCycleSidebar] = useState(true); const [cycleSidebar, setCycleSidebar] = useState(true);
@ -117,8 +112,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
issues={issues?.filter((i) => !i.cycle_id) ?? []} issues={issues?.filter((i) => !i.cycle_id) ?? []}
handleOnSubmit={handleAddIssuesToCycle} handleOnSubmit={handleAddIssuesToCycle}
/> />
<AppLayout <ProjectAuthorizationWrapper
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -169,7 +163,6 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
<div className={`h-full ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}> <div className={`h-full ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}>
<IssuesView <IssuesView
type="cycle" type="cycle"
userAuth={props}
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
isCompleted={cycleStatus === "completed" ?? false} isCompleted={cycleStatus === "completed" ?? false}
/> />
@ -180,38 +173,9 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
isOpen={cycleSidebar} isOpen={cycleSidebar}
isCompleted={cycleStatus === "completed" ?? false} isCompleted={cycleStatus === "completed" ?? false}
/> />
</AppLayout> </ProjectAuthorizationWrapper>
</IssueViewContextProvider> </IssueViewContextProvider>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default SingleCycle; export default SingleCycle;

View File

@ -5,27 +5,24 @@ import dynamic from "next/dynamic";
import useSWR from "swr"; import useSWR from "swr";
import { PlusIcon } from "@heroicons/react/24/outline"; // headless ui
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import cycleService from "services/cycles.service"; import cycleService from "services/cycles.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { CompletedCyclesListProps, CreateUpdateCycleModal, CyclesList } from "components/cycles"; import { CompletedCyclesListProps, CreateUpdateCycleModal, CyclesList } from "components/cycles";
// 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";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import { SelectCycleType, UserAuth } from "types"; import { SelectCycleType } from "types";
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
// fetching keys // fetch-keys
import { import {
CYCLE_CURRENT_AND_UPCOMING_LIST, CYCLE_CURRENT_AND_UPCOMING_LIST,
CYCLE_DRAFT_LIST, CYCLE_DRAFT_LIST,
@ -44,13 +41,12 @@ const CompletedCyclesList = dynamic<CompletedCyclesListProps>(
} }
); );
const ProjectCycles: NextPage<UserAuth> = (props) => { const ProjectCycles: NextPage = () => {
const [selectedCycle, setSelectedCycle] = useState<SelectCycleType>(); const [selectedCycle, setSelectedCycle] = useState<SelectCycleType>();
const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false); const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
const { const router = useRouter();
query: { workspaceSlug, projectId }, const { workspaceSlug, projectId } = router.query;
} = useRouter();
const { data: activeProject } = useSWR( const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
@ -82,11 +78,10 @@ const ProjectCycles: NextPage<UserAuth> = (props) => {
}, [createUpdateCycleModal]); }, [createUpdateCycleModal]);
return ( return (
<AppLayout <ProjectAuthorizationWrapper
meta={{ meta={{
title: "Plane - Cycles", title: "Plane - Cycles",
}} }}
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -195,37 +190,8 @@ const ProjectCycles: NextPage<UserAuth> = (props) => {
</div> </div>
</div> </div>
</div> </div>
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ProjectCycles; export default ProjectCycles;

View File

@ -9,10 +9,8 @@ import useSWR, { mutate } from "swr";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// services // services
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { import {
IssueDescriptionForm, IssueDescriptionForm,
@ -27,8 +25,8 @@ import {
import { Loader, CustomMenu } from "components/ui"; import { Loader, CustomMenu } from "components/ui";
import { Breadcrumbs } from "components/breadcrumbs"; import { Breadcrumbs } from "components/breadcrumbs";
// types // types
import { IIssue, UserAuth } from "types"; import { IIssue } from "types";
import type { GetServerSidePropsContext, NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS, SUB_ISSUES } from "constants/fetch-keys"; import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS, SUB_ISSUES } from "constants/fetch-keys";
@ -47,7 +45,7 @@ const defaultValues = {
labels_list: [], labels_list: [],
}; };
const IssueDetailsPage: NextPage<UserAuth> = (props) => { const IssueDetailsPage: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query; const { workspaceSlug, projectId, issueId } = router.query;
@ -122,8 +120,7 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
}, [issueDetails, reset, issueId]); }, [issueDetails, reset, issueId]);
return ( return (
<AppLayout <ProjectAuthorizationWrapper
memberType={props}
noPadding={true} noPadding={true}
bg="secondary" bg="secondary"
breadcrumbs={ breadcrumbs={
@ -191,20 +188,16 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
</CustomMenu> </CustomMenu>
</div> </div>
) : null} ) : null}
<IssueDescriptionForm <IssueDescriptionForm issue={issueDetails} handleFormSubmit={submitChanges} />
issue={issueDetails}
handleFormSubmit={submitChanges}
userAuth={props}
/>
<div className="mt-2 space-y-2"> <div className="mt-2 space-y-2">
<SubIssuesList parentIssue={issueDetails} userAuth={props} /> <SubIssuesList parentIssue={issueDetails} />
</div> </div>
</div> </div>
<div className="flex flex-col gap-3 py-3"> <div className="flex flex-col gap-3 py-3">
<h3 className="text-lg">Attachments</h3> <h3 className="text-lg">Attachments</h3>
<div className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"> <div className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
<IssueAttachmentUpload/> <IssueAttachmentUpload />
<IssueAttachments /> <IssueAttachments />
</div> </div>
</div> </div>
<div className="space-y-5 bg-secondary pt-3"> <div className="space-y-5 bg-secondary pt-3">
@ -219,7 +212,6 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
issueDetail={issueDetails} issueDetail={issueDetails}
submitChanges={submitChanges} submitChanges={submitChanges}
watch={watch} watch={watch}
userAuth={props}
/> />
</div> </div>
</div> </div>
@ -239,37 +231,8 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
</div> </div>
</Loader> </Loader>
)} )}
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default IssueDetailsPage; export default IssueDetailsPage;

View File

@ -2,12 +2,10 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// components // components
@ -18,12 +16,11 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import type { UserAuth } from "types"; import type { NextPage } from "next";
import type { GetServerSidePropsContext, NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECT_DETAILS } from "constants/fetch-keys"; import { PROJECT_DETAILS } from "constants/fetch-keys";
const ProjectIssues: NextPage<UserAuth> = (props) => { const ProjectIssues: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
@ -36,8 +33,7 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
return ( return (
<IssueViewContextProvider> <IssueViewContextProvider>
<AppLayout <ProjectAuthorizationWrapper
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -60,39 +56,10 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
</div> </div>
} }
> >
<IssuesView userAuth={props} /> <IssuesView />
</AppLayout> </ProjectAuthorizationWrapper>
</IssueViewContextProvider> </IssueViewContextProvider>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ProjectIssues; export default ProjectIssues;

View File

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { GetServerSidePropsContext } from "next";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// icons // icons
@ -12,15 +12,13 @@ import {
RectangleGroupIcon, RectangleGroupIcon,
RectangleStackIcon, RectangleStackIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// components // components
@ -32,7 +30,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
// types // types
import { IModule, UserAuth } from "types"; import { IModule } from "types";
// fetch-keys // fetch-keys
import { import {
@ -42,7 +40,7 @@ import {
PROJECT_ISSUES_LIST, PROJECT_ISSUES_LIST,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
const SingleModule: React.FC<UserAuth> = (props) => { const SingleModule: React.FC = () => {
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false); const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
const [moduleSidebar, setModuleSidebar] = useState(true); const [moduleSidebar, setModuleSidebar] = useState(true);
@ -121,8 +119,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
issues={issues?.filter((i) => !i.module_id) ?? []} issues={issues?.filter((i) => !i.module_id) ?? []}
handleOnSubmit={handleAddIssuesToModule} handleOnSubmit={handleAddIssuesToModule}
/> />
<AppLayout <ProjectAuthorizationWrapper
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -173,11 +170,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
{moduleIssues ? ( {moduleIssues ? (
moduleIssues.length > 0 ? ( moduleIssues.length > 0 ? (
<div className={`h-full ${moduleSidebar ? "mr-[24rem]" : ""} duration-300`}> <div className={`h-full ${moduleSidebar ? "mr-[24rem]" : ""} duration-300`}>
<IssuesView <IssuesView type="module" openIssuesListModal={openIssuesListModal} />
type="module"
userAuth={props}
openIssuesListModal={openIssuesListModal}
/>
</div> </div>
) : ( ) : (
<div <div
@ -220,40 +213,10 @@ const SingleModule: React.FC<UserAuth> = (props) => {
module={moduleDetails} module={moduleDetails}
isOpen={moduleSidebar} isOpen={moduleSidebar}
moduleIssues={moduleIssues} moduleIssues={moduleIssues}
userAuth={props}
/> />
</AppLayout> </ProjectAuthorizationWrapper>
</IssueViewContextProvider> </IssueViewContextProvider>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default SingleModule; export default SingleModule;

View File

@ -1,15 +1,11 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
import { PlusIcon } from "@heroicons/react/24/outline";
// image
import emptyModule from "public/empty-state/empty-module.svg";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
@ -19,19 +15,22 @@ import { CreateUpdateModuleModal, SingleModuleCard } from "components/modules";
import { EmptyState, Loader, PrimaryButton } from "components/ui"; import { EmptyState, Loader, PrimaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline";
// images
import emptyModule from "public/empty-state/empty-module.svg";
// types // types
import { IModule, SelectModuleType } from "types/modules"; import { IModule, SelectModuleType } from "types/modules";
import { UserAuth } from "types"; import type { NextPage } from "next";
import type { NextPage, GetServerSidePropsContext } from "next";
// fetch-keys // fetch-keys
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
const ProjectModules: NextPage<UserAuth> = (props) => { const ProjectModules: NextPage = () => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const [selectedModule, setSelectedModule] = useState<SelectModuleType>(); const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
const [createUpdateModule, setCreateUpdateModule] = useState(false); const [createUpdateModule, setCreateUpdateModule] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { data: activeProject } = useSWR( const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId
@ -60,11 +59,10 @@ const ProjectModules: NextPage<UserAuth> = (props) => {
}, [createUpdateModule]); }, [createUpdateModule]);
return ( return (
<AppLayout <ProjectAuthorizationWrapper
meta={{ meta={{
title: "Plane - Modules", title: "Plane - Modules",
}} }}
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -124,37 +122,8 @@ const ProjectModules: NextPage<UserAuth> = (props) => {
<Loader.Item height="100px" /> <Loader.Item height="100px" />
</Loader> </Loader>
)} )}
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ProjectModules; export default ProjectModules;

View File

@ -13,8 +13,6 @@ import { TwitterPicker } from "react-color";
// react-beautiful-dnd // react-beautiful-dnd
import { DragDropContext, DropResult } from "react-beautiful-dnd"; import { DragDropContext, DropResult } from "react-beautiful-dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable"; import StrictModeDroppable from "components/dnd/StrictModeDroppable";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
import pagesService from "services/pages.service"; import pagesService from "services/pages.service";
@ -22,7 +20,7 @@ import issuesService from "services/issues.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages"; import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages";
// ui // ui
@ -43,8 +41,8 @@ import { renderShortTime } from "helpers/date-time.helper";
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
import { orderArrayBy } from "helpers/array.helper"; import { orderArrayBy } from "helpers/array.helper";
// types // types
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
import { IIssueLabels, IPage, IPageBlock, UserAuth } from "types"; import { IIssueLabels, IPage, IPageBlock } from "types";
// fetch-keys // fetch-keys
import { import {
PAGE_BLOCKS_LIST, PAGE_BLOCKS_LIST,
@ -53,7 +51,7 @@ import {
PROJECT_ISSUE_LABELS, PROJECT_ISSUE_LABELS,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
const SinglePage: NextPage<UserAuth> = (props) => { const SinglePage: NextPage = () => {
const [createBlockForm, setCreateBlockForm] = useState(false); const [createBlockForm, setCreateBlockForm] = useState(false);
const scrollToRef = useRef<HTMLDivElement>(null); const scrollToRef = useRef<HTMLDivElement>(null);
@ -272,11 +270,10 @@ const SinglePage: NextPage<UserAuth> = (props) => {
}, [reset, pageDetails]); }, [reset, pageDetails]);
return ( return (
<AppLayout <ProjectAuthorizationWrapper
meta={{ meta={{
title: "Plane - Pages", title: "Plane - Pages",
}} }}
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -506,37 +503,8 @@ const SinglePage: NextPage<UserAuth> = (props) => {
<Loader.Item height="200px" /> <Loader.Item height="200px" />
</Loader> </Loader>
)} )}
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default SinglePage; export default SinglePage;

View File

@ -1,15 +1,12 @@
import { useState } from "react"; import { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import type { GetServerSidePropsContext, NextPage } from "next";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// react-hook-form // react-hook-form
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// headless ui // headless ui
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
// services // services
@ -20,16 +17,17 @@ import useToast from "hooks/use-toast";
// icons // icons
import { PlusIcon } from "components/icons"; import { PlusIcon } from "components/icons";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { RecentPagesList, CreateUpdatePageModal, TPagesListProps } from "components/pages"; import { RecentPagesList, CreateUpdatePageModal, TPagesListProps } from "components/pages";
// ui // ui
import { Input, PrimaryButton } from "components/ui"; import { Input, PrimaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { ListBulletIcon, RectangleGroupIcon, Squares2X2Icon } from "@heroicons/react/20/solid"; import { ListBulletIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
// types // types
import { IPage, TPageViewProps, UserAuth } from "types"; import { IPage, TPageViewProps } from "types";
import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { import {
ALL_PAGES_LIST, ALL_PAGES_LIST,
@ -66,7 +64,7 @@ const OtherPagesList = dynamic<TPagesListProps>(
} }
); );
const ProjectPages: NextPage<UserAuth> = (props) => { const ProjectPages: NextPage = () => {
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false); const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
const [viewType, setViewType] = useState<TPageViewProps>("list"); const [viewType, setViewType] = useState<TPageViewProps>("list");
@ -153,11 +151,10 @@ const ProjectPages: NextPage<UserAuth> = (props) => {
isOpen={createUpdatePageModal} isOpen={createUpdatePageModal}
handleClose={() => setCreateUpdatePageModal(false)} handleClose={() => setCreateUpdatePageModal(false)}
/> />
<AppLayout <ProjectAuthorizationWrapper
meta={{ meta={{
title: "Plane - Pages", title: "Plane - Pages",
}} }}
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -266,38 +263,9 @@ const ProjectPages: NextPage<UserAuth> = (props) => {
</Tab.Group> </Tab.Group>
</div> </div>
</div> </div>
</AppLayout> </ProjectAuthorizationWrapper>
</> </>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ProjectPages; export default ProjectPages;

View File

@ -6,10 +6,8 @@ import Image from "next/image";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// lib
import { requiredAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// hooks // hooks
@ -19,30 +17,20 @@ import { CustomSelect, Loader, SecondaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import { IProject, IWorkspace } from "types"; import { IProject, IWorkspace } from "types";
import type { NextPage, GetServerSidePropsContext } 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";
type TControlSettingsProps = {
isMember: boolean;
isOwner: boolean;
isViewer: boolean;
isGuest: boolean;
};
const defaultValues: Partial<IProject> = { const defaultValues: Partial<IProject> = {
project_lead: null, project_lead: null,
default_assignee: null, default_assignee: null,
}; };
const ControlSettings: NextPage<TControlSettingsProps> = (props) => { const ControlSettings: NextPage = () => {
const { isMember, isOwner, isViewer, isGuest } = props;
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { const router = useRouter();
query: { workspaceSlug, projectId }, const { workspaceSlug, projectId } = router.query;
} = useRouter();
const { data: projectDetails } = useSWR<IProject>( const { data: projectDetails } = useSWR<IProject>(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
@ -102,9 +90,9 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
console.log(err); console.log(err);
}); });
}; };
return ( return (
<AppLayout <ProjectAuthorizationWrapper
memberType={{ isMember, isOwner, isViewer, isGuest }}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -114,7 +102,6 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
<BreadcrumbItem title="Control Settings" /> <BreadcrumbItem title="Control Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-8 sm:space-y-12"> <div className="space-y-8 sm:space-y-12">
@ -245,24 +232,8 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
</div> </div>
</div> </div>
</form> </form>
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ControlSettings; export default ControlSettings;

View File

@ -8,29 +8,25 @@ import useSWR, { mutate } from "swr";
import estimatesService from "services/estimates.service"; import estimatesService from "services/estimates.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
// lib
import { requiredAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates"; import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
//hooks //hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { Loader, PrimaryButton } from "components/ui"; import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import { IEstimate, UserAuth, IProject } from "types"; import { IEstimate, IProject } from "types";
import type { GetServerSidePropsContext, 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";
const EstimatesSettings: NextPage<UserAuth> = (props) => { const EstimatesSettings: NextPage = () => {
const { isMember, isOwner, isViewer, isGuest } = props;
const [estimateFormOpen, setEstimateFormOpen] = useState(false); const [estimateFormOpen, setEstimateFormOpen] = useState(false);
const [isUpdating, setIsUpdating] = useState(false); const [isUpdating, setIsUpdating] = useState(false);
@ -87,8 +83,7 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
return ( return (
<> <>
<AppLayout <ProjectAuthorizationWrapper
memberType={{ isMember, isOwner, isViewer, isGuest }}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -98,7 +93,6 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Estimates Settings" /> <BreadcrumbItem title="Estimates Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<CreateUpdateEstimateModal <CreateUpdateEstimateModal
isCreate={estimateToUpdate ? true : false} isCreate={estimateToUpdate ? true : false}
@ -154,25 +148,9 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
</> </>
</section> </section>
)} )}
</AppLayout> </ProjectAuthorizationWrapper>
</> </>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default EstimatesSettings; export default EstimatesSettings;

View File

@ -7,10 +7,8 @@ import useSWR, { mutate } from "swr";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
import trackEventServices from "services/track-event.service"; import trackEventServices from "services/track-event.service";
// lib
import { requiredAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
@ -20,12 +18,12 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons"; import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons";
import { DocumentTextIcon } from "@heroicons/react/24/outline"; import { DocumentTextIcon } from "@heroicons/react/24/outline";
// types // types
import { IProject, UserAuth } from "types"; import { IProject } from "types";
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
const FeaturesSettings: NextPage<UserAuth> = (props) => { const FeaturesSettings: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
@ -79,8 +77,7 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
}; };
return ( return (
<AppLayout <ProjectAuthorizationWrapper
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -90,7 +87,6 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Features Settings" /> <BreadcrumbItem title="Features Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<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>
@ -269,24 +265,8 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
</a> </a>
</div> </div>
</section> </section>
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default FeaturesSettings; export default FeaturesSettings;

View File

@ -7,11 +7,8 @@ import useSWR, { mutate } from "swr";
// react-hook-form // react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { IProject, IWorkspace, UserAuth } from "types";
// lib
import { requiredAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// components // components
@ -31,7 +28,8 @@ import {
} from "components/ui"; } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import type { NextPage, GetServerSidePropsContext } from "next"; import { IProject, IWorkspace } from "types";
import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
// constants // constants
@ -44,10 +42,8 @@ const defaultValues: Partial<IProject> = {
network: 0, network: 0,
}; };
const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGuest }) => { const GeneralSettings: NextPage = () => {
const [selectProject, setSelectedProject] = useState<string | null>(null); const [selectProject, setSelectedProject] = useState<string | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -136,8 +132,7 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
}; };
return ( return (
<AppLayout <ProjectAuthorizationWrapper
memberType={{ isMember, isOwner, isViewer, isGuest }}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -147,7 +142,6 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
<BreadcrumbItem title="General Settings" /> <BreadcrumbItem title="General Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<DeleteProjectModal <DeleteProjectModal
data={projectDetails ?? null} data={projectDetails ?? null}
@ -366,24 +360,8 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
</div> </div>
</div> </div>
</form> </form>
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default GeneralSettings; export default GeneralSettings;

View File

@ -4,10 +4,8 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// services // services
import IntegrationService from "services/integration"; import IntegrationService from "services/integration";
import projectService from "services/project.service"; import projectService from "services/project.service";
@ -19,14 +17,12 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { PlusIcon, PuzzlePieceIcon } from "@heroicons/react/24/outline"; import { PlusIcon, PuzzlePieceIcon } from "@heroicons/react/24/outline";
// types // types
import { IProject, UserAuth } from "types"; import { IProject } from "types";
import type { NextPageContext, NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
const ProjectIntegrations: NextPage<UserAuth> = (props) => { const ProjectIntegrations: NextPage = () => {
const { isMember, isOwner, isViewer, isGuest } = props;
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
@ -46,8 +42,7 @@ const ProjectIntegrations: NextPage<UserAuth> = (props) => {
); );
return ( return (
<AppLayout <ProjectAuthorizationWrapper
memberType={{ isMember, isOwner, isViewer, isGuest }}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -57,7 +52,6 @@ const ProjectIntegrations: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Integrations" /> <BreadcrumbItem title="Integrations" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
{workspaceIntegrations ? ( {workspaceIntegrations ? (
workspaceIntegrations.length > 0 ? ( workspaceIntegrations.length > 0 ? (
@ -97,24 +91,8 @@ const ProjectIntegrations: NextPage<UserAuth> = (props) => {
<Loader.Item height="40px" /> <Loader.Item height="40px" />
</Loader> </Loader>
)} )}
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: NextPageContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ProjectIntegrations; export default ProjectIntegrations;

View File

@ -10,7 +10,7 @@ import issuesService from "services/issues.service";
// lib // lib
import { requiredAdmin } from "lib/auth"; import { requiredAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { import {
CreateUpdateLabelInline, CreateUpdateLabelInline,
@ -29,9 +29,7 @@ import type { GetServerSidePropsContext, 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";
const LabelsSettings: NextPage<UserAuth> = (props) => { const LabelsSettings: NextPage = () => {
const { isMember, isOwner, isViewer, isGuest } = props;
// create/edit label form // create/edit label form
const [labelForm, setLabelForm] = useState(false); const [labelForm, setLabelForm] = useState(false);
@ -99,8 +97,7 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
handleClose={() => setLabelsListModal(false)} handleClose={() => setLabelsListModal(false)}
parent={parentLabel} parent={parentLabel}
/> />
<AppLayout <ProjectAuthorizationWrapper
memberType={{ isMember, isOwner, isViewer, isGuest }}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -110,7 +107,6 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Labels Settings" /> <BreadcrumbItem title="Labels Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<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">
@ -182,25 +178,9 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
</> </>
</div> </div>
</section> </section>
</AppLayout> </ProjectAuthorizationWrapper>
</> </>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default LabelsSettings; export default LabelsSettings;

View File

@ -8,12 +8,10 @@ import useSWR from "swr";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// lib
import { requiredAdmin } from "lib/auth";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import AppLayout from "layouts/app-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";
@ -23,8 +21,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
// types // types
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
import { UserAuth } from "types";
// fetch-keys // fetch-keys
import { import {
PROJECT_DETAILS, PROJECT_DETAILS,
@ -35,7 +32,7 @@ import {
// constants // constants
import { ROLE } from "constants/workspace"; import { ROLE } from "constants/workspace";
const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGuest }) => { const MembersSettings: NextPage = () => {
const [inviteModal, setInviteModal] = useState(false); const [inviteModal, setInviteModal] = useState(false);
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null); const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null); const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null);
@ -148,8 +145,7 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
setIsOpen={setInviteModal} setIsOpen={setInviteModal}
members={members} members={members}
/> />
<AppLayout <ProjectAuthorizationWrapper
memberType={{ isMember, isOwner, isViewer, isGuest }}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -159,7 +155,6 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
<BreadcrumbItem title="Members Settings" /> <BreadcrumbItem title="Members Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<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">
@ -274,25 +269,9 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
</div> </div>
)} )}
</section> </section>
</AppLayout> </ProjectAuthorizationWrapper>
</> </>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default MembersSettings; export default MembersSettings;

View File

@ -4,13 +4,11 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredAdmin } from "lib/auth";
// services // services
import stateService from "services/state.service"; import stateService from "services/state.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { import {
CreateUpdateStateInline, CreateUpdateStateInline,
@ -26,14 +24,11 @@ import { PlusIcon } from "@heroicons/react/24/outline";
// helpers // helpers
import { getStatesList, orderStateGroups } from "helpers/state.helper"; import { getStatesList, orderStateGroups } from "helpers/state.helper";
// types // types
import { UserAuth } from "types"; import type { NextPage } from "next";
import type { NextPage, GetServerSidePropsContext } from "next";
// fetch-keys // fetch-keys
import { PROJECT_DETAILS, STATE_LIST } from "constants/fetch-keys"; import { PROJECT_DETAILS, STATE_LIST } from "constants/fetch-keys";
const StatesSettings: NextPage<UserAuth> = (props) => { const StatesSettings: NextPage = () => {
const { isMember, isOwner, isViewer, isGuest } = props;
const [activeGroup, setActiveGroup] = useState<StateGroup>(null); const [activeGroup, setActiveGroup] = useState<StateGroup>(null);
const [selectedState, setSelectedState] = useState<string | null>(null); const [selectedState, setSelectedState] = useState<string | null>(null);
const [selectDeleteState, setSelectDeleteState] = useState<string | null>(null); const [selectDeleteState, setSelectDeleteState] = useState<string | null>(null);
@ -64,8 +59,7 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
data={statesList?.find((s) => s.id === selectDeleteState) ?? null} data={statesList?.find((s) => s.id === selectDeleteState) ?? null}
onClose={() => setSelectDeleteState(null)} onClose={() => setSelectDeleteState(null)}
/> />
<AppLayout <ProjectAuthorizationWrapper
memberType={{ isMember, isOwner, isViewer, isGuest }}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -75,7 +69,6 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="States Settings" /> <BreadcrumbItem title="States Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<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">
@ -149,25 +142,9 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
)} )}
</div> </div>
</div> </div>
</AppLayout> </ProjectAuthorizationWrapper>
</> </>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default StatesSettings; export default StatesSettings;

View File

@ -1,30 +1,28 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { GetServerSidePropsContext } from "next";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
import viewsService from "services/views.service"; import viewsService from "services/views.service";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
import { IssuesFilterView, IssuesView } from "components/core";
// ui // ui
import { CustomMenu, PrimaryButton } from "components/ui"; import { CustomMenu, PrimaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // icons
import { UserAuth } from "types"; import { PlusIcon } from "@heroicons/react/24/outline";
import { StackedLayersIcon } from "components/icons";
// helpers
import { truncateText } from "helpers/string.helper";
// fetch-keys // fetch-keys
import { PROJECT_DETAILS, VIEWS_LIST, VIEW_DETAILS } from "constants/fetch-keys"; import { PROJECT_DETAILS, VIEWS_LIST, VIEW_DETAILS } from "constants/fetch-keys";
import { IssuesFilterView, IssuesView } from "components/core";
import { PlusIcon } from "@heroicons/react/24/outline";
import { truncateText } from "helpers/string.helper";
import { StackedLayersIcon } from "components/icons";
const SingleView: React.FC<UserAuth> = (props) => { const SingleView: React.FC = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query; const { workspaceSlug, projectId, viewId } = router.query;
@ -56,8 +54,7 @@ const SingleView: React.FC<UserAuth> = (props) => {
return ( return (
<IssueViewContextProvider> <IssueViewContextProvider>
<AppLayout <ProjectAuthorizationWrapper
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -104,39 +101,10 @@ const SingleView: React.FC<UserAuth> = (props) => {
</div> </div>
} }
> >
<IssuesView userAuth={props} /> <IssuesView />
</AppLayout> </ProjectAuthorizationWrapper>
</IssueViewContextProvider> </IssueViewContextProvider>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default SingleView; export default SingleView;

View File

@ -4,40 +4,32 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import viewsService from "services/views.service"; import viewsService from "services/views.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
//icons //icons
import { PlusIcon } from "components/icons"; import { PlusIcon } from "components/icons";
// images
// image
import emptyView from "public/empty-state/empty-view.svg"; import emptyView from "public/empty-state/empty-view.svg";
// fetching keys // fetching keys
import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys"; import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys";
// components // components
import { PrimaryButton, Loader, EmptyState } from "components/ui"; import { PrimaryButton, Loader, EmptyState } from "components/ui";
import { DeleteViewModal, CreateUpdateViewModal, SingleViewItem } from "components/views"; import { DeleteViewModal, CreateUpdateViewModal, SingleViewItem } from "components/views";
// types // types
import { IView, UserAuth } from "types"; import { IView } from "types";
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
const ProjectViews: NextPage<UserAuth> = (props) => { const ProjectViews: NextPage = () => {
const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false); const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false);
const [selectedView, setSelectedView] = useState<IView | null>(null); const [selectedView, setSelectedView] = useState<IView | null>(null);
const { const router = useRouter();
query: { workspaceSlug, projectId }, const { workspaceSlug, projectId } = router.query;
} = useRouter();
const { data: activeProject } = useSWR( const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
@ -54,11 +46,10 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
); );
return ( return (
<AppLayout <ProjectAuthorizationWrapper
meta={{ meta={{
title: "Plane - Views", title: "Plane - Views",
}} }}
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -116,37 +107,8 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
<Loader.Item height="30px" /> <Loader.Item height="30px" />
</Loader> </Loader>
)} )}
</AppLayout> </ProjectAuthorizationWrapper>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ProjectViews; export default ProjectViews;

View File

@ -1,15 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
// lib
import { requiredAuth } from "lib/auth";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// hooks // hooks
import useProjects from "hooks/use-projects"; import useProjects from "hooks/use-projects";
import useWorkspaces from "hooks/use-workspaces"; import useWorkspaces from "hooks/use-workspaces";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components // components
import { JoinProjectModal } from "components/project/join-project-modal"; import { JoinProjectModal } from "components/project/join-project-modal";
import { DeleteProjectModal, SingleProjectCard } from "components/project"; import { DeleteProjectModal, SingleProjectCard } from "components/project";
@ -19,7 +20,7 @@ import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import type { GetServerSidePropsContext, NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECT_MEMBERS } from "constants/fetch-keys"; import { PROJECT_MEMBERS } from "constants/fetch-keys";
// image // image
@ -37,7 +38,7 @@ const ProjectsPage: NextPage = () => {
const [selectedProjectToJoin, setSelectedProjectToJoin] = useState<string | null>(null); const [selectedProjectToJoin, setSelectedProjectToJoin] = useState<string | null>(null);
return ( return (
<AppLayout <WorkspaceAuthorizationLayout
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Projects`} /> <BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Projects`} />
@ -113,29 +114,8 @@ const ProjectsPage: NextPage = () => {
<Loader.Item height="100px" /> <Loader.Item height="100px" />
</Loader> </Loader>
)} )}
</AppLayout> </WorkspaceAuthorizationLayout>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
return {
props: {
user,
},
};
};
export default ProjectsPage; export default ProjectsPage;

View File

@ -4,28 +4,19 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// ui // ui
import { SecondaryButton } from "components/ui"; import { SecondaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import type { NextPage, GetServerSideProps } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { WORKSPACE_DETAILS } from "constants/fetch-keys"; import { WORKSPACE_DETAILS } from "constants/fetch-keys";
type TBillingSettingsProps = { const BillingSettings: NextPage = () => {
isOwner: boolean;
isMember: boolean;
isViewer: boolean;
isGuest: boolean;
};
const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
const { const {
query: { workspaceSlug }, query: { workspaceSlug },
} = useRouter(); } = useRouter();
@ -36,72 +27,44 @@ const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
); );
return ( return (
<> <WorkspaceAuthorizationLayout
<AppLayout breadcrumbs={
memberType={props} <Breadcrumbs>
breadcrumbs={ <BreadcrumbItem
<Breadcrumbs> title={`${activeWorkspace?.name ?? "Workspace"}`}
<BreadcrumbItem link={`/${workspaceSlug}`}
title={`${activeWorkspace?.name ?? "Workspace"}`} />
link={`/${workspaceSlug}`} <BreadcrumbItem title="Members Settings" />
/> </Breadcrumbs>
<BreadcrumbItem title="Members Settings" /> }
</Breadcrumbs> >
} <section className="space-y-8">
settingsLayout <div>
> <h3 className="text-3xl font-bold leading-6 text-gray-900">Billing & Plans</h3>
<section className="space-y-8"> <p className="mt-4 text-sm text-gray-500">[Free launch preview] plan Pro</p>
</div>
<div className="space-y-8 md:w-2/3">
<div> <div>
<h3 className="text-3xl font-bold leading-6 text-gray-900">Billing & Plans</h3> <div className="w-80 rounded-md border bg-white p-4 text-center">
<p className="mt-4 text-sm text-gray-500">[Free launch preview] plan Pro</p> <h4 className="text-md mb-1 leading-6 text-gray-900">Payment due</h4>
</div> <h2 className="text-3xl font-extrabold">--</h2>
<div className="space-y-8 md:w-2/3">
<div>
<div className="w-80 rounded-md border bg-white p-4 text-center">
<h4 className="text-md mb-1 leading-6 text-gray-900">Payment due</h4>
<h2 className="text-3xl font-extrabold">--</h2>
</div>
</div>
<div>
<h4 className="text-md mb-1 leading-6 text-gray-900">Current plan</h4>
<p className="mb-3 text-sm text-gray-500">You are currently using the free plan</p>
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton>
</a>
</div>
<div>
<h4 className="text-md mb-1 leading-6 text-gray-900">Billing history</h4>
<p className="mb-3 text-sm text-gray-500">There are no invoices to display</p>
</div> </div>
</div> </div>
</section> <div>
</AppLayout> <h4 className="text-md mb-1 leading-6 text-gray-900">Current plan</h4>
</> <p className="mb-3 text-sm text-gray-500">You are currently using the free plan</p>
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton>
</a>
</div>
<div>
<h4 className="text-md mb-1 leading-6 text-gray-900">Billing history</h4>
<p className="mb-3 text-sm text-gray-500">There are no invoices to display</p>
</div>
</div>
</section>
</WorkspaceAuthorizationLayout>
); );
}; };
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const workspaceSlug = ctx.params?.workspaceSlug as string;
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
if (memberDetail === null) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default BillingSettings; export default BillingSettings;

View File

@ -1,58 +1,30 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import IntegrationGuide from "components/integration/guide"; import IntegrationGuide from "components/integration/guide";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import { UserAuth } from "types"; import type { NextPage } from "next";
import type { GetServerSideProps, NextPage } from "next";
const ImportExport: NextPage<UserAuth> = (props) => { const ImportExport: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
return ( return (
<AppLayout <WorkspaceAuthorizationLayout
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title={`${workspaceSlug ?? "Workspace"}`} link={`/${workspaceSlug}`} /> <BreadcrumbItem title={`${workspaceSlug ?? "Workspace"}`} link={`/${workspaceSlug}`} />
<BreadcrumbItem title="Members Settings" /> <BreadcrumbItem title="Members Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<IntegrationGuide /> <IntegrationGuide />
</AppLayout> </WorkspaceAuthorizationLayout>
); );
}; };
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const workspaceSlug = ctx.params?.workspaceSlug as string;
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
if (memberDetail === null) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default ImportExport; export default ImportExport;

View File

@ -7,29 +7,26 @@ import useSWR, { mutate } from "swr";
// react-hook-form // react-hook-form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// icons
import { LinkIcon } from "@heroicons/react/24/outline";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
import fileService from "services/file.service"; import fileService from "services/file.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components // components
import { ImageUploadModal } from "components/core"; import { ImageUploadModal } from "components/core";
import { DeleteWorkspaceModal } from "components/workspace"; import { DeleteWorkspaceModal } from "components/workspace";
// ui // ui
import { Spinner, Input, CustomSelect, OutlineButton, SecondaryButton } from "components/ui"; import { Spinner, Input, CustomSelect, OutlineButton, SecondaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { LinkIcon } from "@heroicons/react/24/outline";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// types // types
import type { IWorkspace, UserAuth } from "types"; import type { IWorkspace } from "types";
import type { GetServerSideProps, NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { WORKSPACE_DETAILS, USER_WORKSPACES } from "constants/fetch-keys"; import { WORKSPACE_DETAILS, USER_WORKSPACES } from "constants/fetch-keys";
// constants // constants
@ -42,7 +39,7 @@ const defaultValues: Partial<IWorkspace> = {
logo: null, logo: null,
}; };
const WorkspaceSettings: NextPage<UserAuth> = (props) => { const WorkspaceSettings: NextPage = () => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isImageUploading, setIsImageUploading] = useState(false); const [isImageUploading, setIsImageUploading] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
@ -107,8 +104,7 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
}; };
return ( return (
<AppLayout <WorkspaceAuthorizationLayout
memberType={props}
meta={{ meta={{
title: "Plane - Workspace Settings", title: "Plane - Workspace Settings",
}} }}
@ -117,7 +113,6 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Settings`} /> <BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Settings`} />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<ImageUploadModal <ImageUploadModal
isOpen={isImageUploadModalOpen} isOpen={isImageUploadModalOpen}
@ -282,32 +277,8 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
<Spinner /> <Spinner />
</div> </div>
)} )}
</AppLayout> </WorkspaceAuthorizationLayout>
); );
}; };
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const workspaceSlug = ctx.params?.workspaceSlug as string;
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
if (memberDetail === null) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default WorkspaceSettings; export default WorkspaceSettings;

View File

@ -7,22 +7,19 @@ import useSWR from "swr";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
import IntegrationService from "services/integration"; import IntegrationService from "services/integration";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// componentss // components
import OAuthPopUp from "components/popup"; import OAuthPopUp from "components/popup";
// ui // ui
import { Loader } from "components/ui"; import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import type { NextPage, GetServerSideProps } from "next"; import type { NextPage } from "next";
import { UserAuth } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys"; import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys";
const WorkspaceIntegrations: NextPage<UserAuth> = (props) => { const WorkspaceIntegrations: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
@ -36,66 +33,38 @@ const WorkspaceIntegrations: NextPage<UserAuth> = (props) => {
); );
return ( return (
<> <WorkspaceAuthorizationLayout
<AppLayout breadcrumbs={
memberType={props} <Breadcrumbs>
breadcrumbs={ <BreadcrumbItem
<Breadcrumbs> title={`${activeWorkspace?.name ?? "Workspace"}`}
<BreadcrumbItem link={`/${workspaceSlug}`}
title={`${activeWorkspace?.name ?? "Workspace"}`} />
link={`/${workspaceSlug}`} <BreadcrumbItem title="Integrations" />
/> </Breadcrumbs>
<BreadcrumbItem title="Integrations" /> }
</Breadcrumbs> >
} <section className="space-y-8">
settingsLayout <h3 className="text-2xl font-semibold">Integrations</h3>
> <div className="space-y-5">
<section className="space-y-8"> {appIntegrations ? (
<h3 className="text-2xl font-semibold">Integrations</h3> appIntegrations.map((integration) => (
<div className="space-y-5"> <OAuthPopUp
{appIntegrations ? ( key={integration.id}
appIntegrations.map((integration) => ( workspaceSlug={workspaceSlug}
<OAuthPopUp integration={integration}
key={integration.id} />
workspaceSlug={workspaceSlug} ))
integration={integration} ) : (
/> <Loader className="space-y-5">
)) <Loader.Item height="60px" />
) : ( <Loader.Item height="60px" />
<Loader className="space-y-5"> </Loader>
<Loader.Item height="60px" /> )}
<Loader.Item height="60px" /> </div>
</Loader> </section>
)} </WorkspaceAuthorizationLayout>
</div>
</section>
</AppLayout>
</>
); );
}; };
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const workspaceSlug = ctx.params?.workspaceSlug as string;
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
if (memberDetail === null) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default WorkspaceIntegrations; export default WorkspaceIntegrations;

View File

@ -5,14 +5,12 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components // components
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove"; import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal"; import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
@ -22,14 +20,13 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import type { GetServerSideProps, NextPage } from "next"; import type { NextPage } from "next";
import { UserAuth } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
// constants // constants
import { ROLE } from "constants/workspace"; import { ROLE } from "constants/workspace";
const MembersSettings: NextPage<UserAuth> = (props) => { const MembersSettings: NextPage = () => {
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null); const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null); const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null);
const [inviteModal, setInviteModal] = useState(false); const [inviteModal, setInviteModal] = useState(false);
@ -129,8 +126,7 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
workspace_slug={workspaceSlug as string} workspace_slug={workspaceSlug as string}
members={members} members={members}
/> />
<AppLayout <WorkspaceAuthorizationLayout
memberType={props}
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem
@ -140,7 +136,6 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Members Settings" /> <BreadcrumbItem title="Members Settings" />
</Breadcrumbs> </Breadcrumbs>
} }
settingsLayout
> >
<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">
@ -254,33 +249,9 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
</div> </div>
)} )}
</section> </section>
</AppLayout> </WorkspaceAuthorizationLayout>
</> </>
); );
}; };
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const workspaceSlug = ctx.params?.workspaceSlug as string;
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
if (memberDetail === null) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default MembersSettings; export default MembersSettings;

View File

@ -42,7 +42,6 @@ const AppPostInstallation = ({
}; };
export async function getServerSideProps(context: any) { export async function getServerSideProps(context: any) {
console.log(context.query);
return { return {
props: context.query, props: context.query,
}; };

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -136,7 +136,7 @@ class ProjectServices extends APIService {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`) return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response?.data; throw error?.response;
}); });
} }

View File

@ -137,7 +137,7 @@ class WorkspaceService extends APIService {
return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/me/`) return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/me/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response?.data; throw error?.response;
}); });
} }