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";
// hooks
import useUser from "hooks/use-user";
// img
import ProjectSettingImg from "public/project-setting.svg";
// images
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;
type: "project" | "workspace";
};
export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => {
const { actionButton } = props;
export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
const { user } = useUser();
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="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>
<h1 className="text-xl font-medium text-gray-900">
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 ">
{user ? (
<p className="">
You have signed in as {user.email}.{" "}
You have signed in as {user.email}. <br />
<Link href={`/signin?next=${currentPath}`}>
<a className="text-gray-900 font-medium">Sign in</a>
</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";
export * from "./link-modal";
export * from "./not-authorized-view";
export * from "./image-picker-popover";
export * from "./filter-list";
export * from "./feeds";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
export * from "./create-project-modal";
export * from "./delete-project-modal";
export * from "./join-project";
export * from "./sidebar-list";
export * from "./single-integration-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
import { LayerDiagonalIcon } from "components/icons";
// components
import { NotAuthorizedView } from "components/core";
import { NotAuthorizedView, JoinProject } from "components/auth-screens";
import { CommandPalette } from "components/command-palette";
import { JoinProject } from "components/project";
// local components
import Container from "layouts/container";
import AppSidebar from "layouts/app-layout/app-sidebar";
@ -61,7 +60,6 @@ const AppLayout: FC<AppLayoutProps> = ({
}) => {
// states
const [toggleSidebar, setToggleSidebar] = useState(false);
const [isJoiningProject, setIsJoiningProject] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -83,21 +81,6 @@ const AppLayout: FC<AppLayoutProps> = ({
memberType?.isViewer ||
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 (
<Container meta={meta}>
<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">
@ -170,7 +154,7 @@ const AppLayout: FC<AppLayoutProps> = ({
{children}
</div>
) : (
<JoinProject isJoiningProject={isJoiningProject} handleJoin={handleJoin} />
<JoinProject />
)}
</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";
type Props = {
profilePage: boolean;
profilePage?: boolean;
};
const SettingsNavbar: React.FC<Props> = ({ profilePage = false }) => {

View File

@ -4,10 +4,8 @@ import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
// lib
import { requiredAuth } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// services
import userService from "services/user.service";
// components
@ -18,7 +16,7 @@ import {
IssuesStats,
} from "components/workspace";
// types
import type { NextPage, GetServerSidePropsContext } from "next";
import type { NextPage } from "next";
// fetch-keys
import { USER_WORKSPACE_DASHBOARD } from "constants/fetch-keys";
@ -38,14 +36,16 @@ const WorkspacePage: NextPage = () => {
}, [month, workspaceSlug]);
return (
<AppLayout noHeader={true}>
<WorkspaceAuthorizationLayout noHeader>
<div className="h-full w-full">
<div className="flex flex-col gap-8">
<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"
// 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">
{/* <a href="#" target="_blank" rel="noopener noreferrer">
View roadmap
@ -73,29 +73,8 @@ const WorkspacePage: NextPage = () => {
</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;

View File

@ -1,4 +1,5 @@
import React from "react";
import { useRouter } from "next/router";
// headless ui
@ -6,7 +7,7 @@ import { Disclosure, Popover, Transition } from "@headlessui/react";
// icons
import { ChevronDownIcon, PlusIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// hooks
import useIssues from "hooks/use-issues";
// ui
@ -36,166 +37,164 @@ const MyIssuesPage: NextPage = () => {
);
return (
<>
<AppLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="My Issues" />
</Breadcrumbs>
}
right={
<div className="flex items-center gap-2">
{myIssues && myIssues.length > 0 && (
<Popover className="relative">
{({ open }) => (
<>
<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 ${
open ? "bg-gray-100 text-gray-900" : "text-gray-500"
}`}
>
<span>View</span>
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
</Popover.Button>
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="My Issues" />
</Breadcrumbs>
}
right={
<div className="flex items-center gap-2">
{myIssues && myIssues.length > 0 && (
<Popover className="relative">
{({ open }) => (
<>
<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 ${
open ? "bg-gray-100 text-gray-900" : "text-gray-500"
}`}
>
<span>View</span>
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
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">
<div className="relative flex flex-col gap-1 gap-y-4">
<div className="relative flex flex-col gap-1">
<h4 className="text-base text-gray-600">Properties</h4>
<div className="flex flex-wrap items-center gap-2">
{Object.keys(properties).map((key) => (
<button
key={key}
type="button"
className={`rounded border border-theme px-2 py-1 text-xs capitalize ${
properties[key as keyof Properties]
? "border-theme bg-theme text-white"
: ""
}`}
onClick={() => setProperties(key as keyof Properties)}
>
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
</button>
))}
</div>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
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">
<div className="relative flex flex-col gap-1 gap-y-4">
<div className="relative flex flex-col gap-1">
<h4 className="text-base text-gray-600">Properties</h4>
<div className="flex flex-wrap items-center gap-2">
{Object.keys(properties).map((key) => (
<button
key={key}
type="button"
className={`rounded border border-theme px-2 py-1 text-xs capitalize ${
properties[key as keyof Properties]
? "border-theme bg-theme text-white"
: ""
}`}
onClick={() => setProperties(key as keyof Properties)}
>
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
</button>
))}
</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>
)}
</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>
</Popover.Panel>
</Transition>
</>
)}
</>
) : (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
</div>
</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>
</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";
// services
import userService from "services/user.service";
// lib
import { requiredAuth } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import { Feeds } from "components/core";
// ui
import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { Feeds } from "components/core";
// fetch-keys
import { USER_ACTIVITY } from "constants/fetch-keys";
@ -19,7 +16,7 @@ const ProfileActivity = () => {
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
return (
<AppLayout
<WorkspaceAuthorizationLayout
meta={{
title: "Plane - My Profile",
}}
@ -28,7 +25,6 @@ const ProfileActivity = () => {
<BreadcrumbItem title="My Profile Activity" />
</Breadcrumbs>
}
settingsLayout
profilePage
>
{userActivity ? (
@ -43,29 +39,8 @@ const ProfileActivity = () => {
<Loader.Item height="40px" />
</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;

View File

@ -4,8 +4,6 @@ import Image from "next/image";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// lib
import { requiredAuth } from "lib/auth";
// services
import fileService from "services/file.service";
import userService from "services/user.service";
@ -13,7 +11,7 @@ import userService from "services/user.service";
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import { ImageUploadModal } from "components/core";
// ui
@ -22,7 +20,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { UserIcon } from "@heroicons/react/24/outline";
// types
import type { NextPage, GetServerSidePropsContext } from "next";
import type { NextPage } from "next";
import type { IUser } from "types";
// constants
import { USER_ROLES } from "constants/workspace";
@ -123,7 +121,7 @@ const Profile: NextPage = () => {
};
return (
<AppLayout
<WorkspaceAuthorizationLayout
meta={{
title: "Plane - My Profile",
}}
@ -132,7 +130,6 @@ const Profile: NextPage = () => {
<BreadcrumbItem title="My Profile" />
</Breadcrumbs>
}
settingsLayout
profilePage
>
<ImageUploadModal
@ -282,29 +279,8 @@ const Profile: NextPage = () => {
<Spinner />
</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;

View File

@ -3,14 +3,11 @@ import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import { GetServerSidePropsContext } from "next";
// icons
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { CyclesIcon } from "components/icons";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
@ -28,8 +25,6 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// helpers
import { truncateText } from "helpers/string.helper";
import { getDateRangeStatus } from "helpers/date-time.helper";
// types
import { UserAuth } from "types";
// fetch-keys
import {
CYCLE_ISSUES,
@ -39,7 +34,7 @@ import {
PROJECT_ISSUES_LIST,
} from "constants/fetch-keys";
const SingleCycle: React.FC<UserAuth> = (props) => {
const SingleCycle: React.FC = () => {
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
const [cycleSidebar, setCycleSidebar] = useState(true);
@ -117,8 +112,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
issues={issues?.filter((i) => !i.cycle_id) ?? []}
handleOnSubmit={handleAddIssuesToCycle}
/>
<AppLayout
memberType={props}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -169,7 +163,6 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
<div className={`h-full ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}>
<IssuesView
type="cycle"
userAuth={props}
openIssuesListModal={openIssuesListModal}
isCompleted={cycleStatus === "completed" ?? false}
/>
@ -180,38 +173,9 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
isOpen={cycleSidebar}
isCompleted={cycleStatus === "completed" ?? false}
/>
</AppLayout>
</ProjectAuthorizationWrapper>
</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;

View File

@ -5,27 +5,24 @@ import dynamic from "next/dynamic";
import useSWR from "swr";
import { PlusIcon } from "@heroicons/react/24/outline";
// headless ui
import { Tab } from "@headlessui/react";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import cycleService from "services/cycles.service";
import projectService from "services/project.service";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import { CompletedCyclesListProps, CreateUpdateCycleModal, CyclesList } from "components/cycles";
// ui
import { Loader, PrimaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// types
import { SelectCycleType, UserAuth } from "types";
import type { NextPage, GetServerSidePropsContext } from "next";
// fetching keys
import { SelectCycleType } from "types";
import type { NextPage } from "next";
// fetch-keys
import {
CYCLE_CURRENT_AND_UPCOMING_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 [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
const {
query: { workspaceSlug, projectId },
} = useRouter();
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
@ -82,11 +78,10 @@ const ProjectCycles: NextPage<UserAuth> = (props) => {
}, [createUpdateCycleModal]);
return (
<AppLayout
<ProjectAuthorizationWrapper
meta={{
title: "Plane - Cycles",
}}
memberType={props}
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -195,37 +190,8 @@ const ProjectCycles: NextPage<UserAuth> = (props) => {
</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;

View File

@ -9,10 +9,8 @@ import useSWR, { mutate } from "swr";
import { useForm } from "react-hook-form";
// services
import issuesService from "services/issues.service";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import {
IssueDescriptionForm,
@ -27,8 +25,8 @@ import {
import { Loader, CustomMenu } from "components/ui";
import { Breadcrumbs } from "components/breadcrumbs";
// types
import { IIssue, UserAuth } from "types";
import type { GetServerSidePropsContext, NextPage } from "next";
import { IIssue } from "types";
import type { NextPage } from "next";
// fetch-keys
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS, SUB_ISSUES } from "constants/fetch-keys";
@ -47,7 +45,7 @@ const defaultValues = {
labels_list: [],
};
const IssueDetailsPage: NextPage<UserAuth> = (props) => {
const IssueDetailsPage: NextPage = () => {
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
@ -122,8 +120,7 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
}, [issueDetails, reset, issueId]);
return (
<AppLayout
memberType={props}
<ProjectAuthorizationWrapper
noPadding={true}
bg="secondary"
breadcrumbs={
@ -191,20 +188,16 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
</CustomMenu>
</div>
) : null}
<IssueDescriptionForm
issue={issueDetails}
handleFormSubmit={submitChanges}
userAuth={props}
/>
<IssueDescriptionForm issue={issueDetails} handleFormSubmit={submitChanges} />
<div className="mt-2 space-y-2">
<SubIssuesList parentIssue={issueDetails} userAuth={props} />
<SubIssuesList parentIssue={issueDetails} />
</div>
</div>
<div className="flex flex-col gap-3 py-3">
<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">
<IssueAttachmentUpload/>
<IssueAttachments />
<IssueAttachmentUpload />
<IssueAttachments />
</div>
</div>
<div className="space-y-5 bg-secondary pt-3">
@ -219,7 +212,6 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
issueDetail={issueDetails}
submitChanges={submitChanges}
watch={watch}
userAuth={props}
/>
</div>
</div>
@ -239,37 +231,8 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
</div>
</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;

View File

@ -2,12 +2,10 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import projectService from "services/project.service";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
@ -18,12 +16,11 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// types
import type { UserAuth } from "types";
import type { GetServerSidePropsContext, NextPage } from "next";
import type { NextPage } from "next";
// fetch-keys
import { PROJECT_DETAILS } from "constants/fetch-keys";
const ProjectIssues: NextPage<UserAuth> = (props) => {
const ProjectIssues: NextPage = () => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -36,8 +33,7 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
return (
<IssueViewContextProvider>
<AppLayout
memberType={props}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -60,39 +56,10 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
</div>
}
>
<IssuesView userAuth={props} />
</AppLayout>
<IssuesView />
</ProjectAuthorizationWrapper>
</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;

View File

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { GetServerSidePropsContext } from "next";
import useSWR, { mutate } from "swr";
// icons
@ -12,15 +12,13 @@ import {
RectangleGroupIcon,
RectangleStackIcon,
} from "@heroicons/react/24/outline";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import modulesService from "services/modules.service";
import issuesService from "services/issues.service";
// hooks
import useToast from "hooks/use-toast";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
@ -32,7 +30,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// helpers
import { truncateText } from "helpers/string.helper";
// types
import { IModule, UserAuth } from "types";
import { IModule } from "types";
// fetch-keys
import {
@ -42,7 +40,7 @@ import {
PROJECT_ISSUES_LIST,
} from "constants/fetch-keys";
const SingleModule: React.FC<UserAuth> = (props) => {
const SingleModule: React.FC = () => {
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
const [moduleSidebar, setModuleSidebar] = useState(true);
@ -121,8 +119,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
issues={issues?.filter((i) => !i.module_id) ?? []}
handleOnSubmit={handleAddIssuesToModule}
/>
<AppLayout
memberType={props}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -173,11 +170,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
{moduleIssues ? (
moduleIssues.length > 0 ? (
<div className={`h-full ${moduleSidebar ? "mr-[24rem]" : ""} duration-300`}>
<IssuesView
type="module"
userAuth={props}
openIssuesListModal={openIssuesListModal}
/>
<IssuesView type="module" openIssuesListModal={openIssuesListModal} />
</div>
) : (
<div
@ -220,40 +213,10 @@ const SingleModule: React.FC<UserAuth> = (props) => {
module={moduleDetails}
isOpen={moduleSidebar}
moduleIssues={moduleIssues}
userAuth={props}
/>
</AppLayout>
</ProjectAuthorizationWrapper>
</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;

View File

@ -1,15 +1,11 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { PlusIcon } from "@heroicons/react/24/outline";
// image
import emptyModule from "public/empty-state/empty-module.svg";
// layouts
import AppLayout from "layouts/app-layout";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// services
import projectService from "services/project.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 { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// images
import emptyModule from "public/empty-state/empty-module.svg";
// types
import { IModule, SelectModuleType } from "types/modules";
import { UserAuth } from "types";
import type { NextPage, GetServerSidePropsContext } from "next";
import type { NextPage } from "next";
// fetch-keys
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
const ProjectModules: NextPage<UserAuth> = (props) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const ProjectModules: NextPage = () => {
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
const [createUpdateModule, setCreateUpdateModule] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId
@ -60,11 +59,10 @@ const ProjectModules: NextPage<UserAuth> = (props) => {
}, [createUpdateModule]);
return (
<AppLayout
<ProjectAuthorizationWrapper
meta={{
title: "Plane - Modules",
}}
memberType={props}
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -124,37 +122,8 @@ const ProjectModules: NextPage<UserAuth> = (props) => {
<Loader.Item height="100px" />
</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;

View File

@ -13,8 +13,6 @@ import { TwitterPicker } from "react-color";
// react-beautiful-dnd
import { DragDropContext, DropResult } from "react-beautiful-dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import projectService from "services/project.service";
import pagesService from "services/pages.service";
@ -22,7 +20,7 @@ import issuesService from "services/issues.service";
// hooks
import useToast from "hooks/use-toast";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages";
// ui
@ -43,8 +41,8 @@ import { renderShortTime } from "helpers/date-time.helper";
import { copyTextToClipboard } from "helpers/string.helper";
import { orderArrayBy } from "helpers/array.helper";
// types
import type { NextPage, GetServerSidePropsContext } from "next";
import { IIssueLabels, IPage, IPageBlock, UserAuth } from "types";
import type { NextPage } from "next";
import { IIssueLabels, IPage, IPageBlock } from "types";
// fetch-keys
import {
PAGE_BLOCKS_LIST,
@ -53,7 +51,7 @@ import {
PROJECT_ISSUE_LABELS,
} from "constants/fetch-keys";
const SinglePage: NextPage<UserAuth> = (props) => {
const SinglePage: NextPage = () => {
const [createBlockForm, setCreateBlockForm] = useState(false);
const scrollToRef = useRef<HTMLDivElement>(null);
@ -272,11 +270,10 @@ const SinglePage: NextPage<UserAuth> = (props) => {
}, [reset, pageDetails]);
return (
<AppLayout
<ProjectAuthorizationWrapper
meta={{
title: "Plane - Pages",
}}
memberType={props}
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -506,37 +503,8 @@ const SinglePage: NextPage<UserAuth> = (props) => {
<Loader.Item height="200px" />
</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;

View File

@ -1,15 +1,12 @@
import { useState } from "react";
import { useRouter } from "next/router";
import type { GetServerSidePropsContext, NextPage } from "next";
import dynamic from "next/dynamic";
import useSWR, { mutate } from "swr";
// react-hook-form
import { useForm } from "react-hook-form";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// headless ui
import { Tab } from "@headlessui/react";
// services
@ -20,16 +17,17 @@ import useToast from "hooks/use-toast";
// icons
import { PlusIcon } from "components/icons";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import { RecentPagesList, CreateUpdatePageModal, TPagesListProps } from "components/pages";
// ui
import { Input, PrimaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { ListBulletIcon, RectangleGroupIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import { ListBulletIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
// types
import { IPage, TPageViewProps, UserAuth } from "types";
import { IPage, TPageViewProps } from "types";
import type { NextPage } from "next";
// fetch-keys
import {
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 [viewType, setViewType] = useState<TPageViewProps>("list");
@ -153,11 +151,10 @@ const ProjectPages: NextPage<UserAuth> = (props) => {
isOpen={createUpdatePageModal}
handleClose={() => setCreateUpdatePageModal(false)}
/>
<AppLayout
<ProjectAuthorizationWrapper
meta={{
title: "Plane - Pages",
}}
memberType={props}
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -266,38 +263,9 @@ const ProjectPages: NextPage<UserAuth> = (props) => {
</Tab.Group>
</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;

View File

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

View File

@ -8,29 +8,25 @@ import useSWR, { mutate } from "swr";
import estimatesService from "services/estimates.service";
import projectService from "services/project.service";
// lib
import { requiredAdmin } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
//hooks
import useToast from "hooks/use-toast";
// ui
import { Loader, PrimaryButton } from "components/ui";
import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// types
import { IEstimate, UserAuth, IProject } from "types";
import type { GetServerSidePropsContext, NextPage } from "next";
import { IEstimate, IProject } from "types";
import type { NextPage } from "next";
// fetch-keys
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
const EstimatesSettings: NextPage<UserAuth> = (props) => {
const { isMember, isOwner, isViewer, isGuest } = props;
const EstimatesSettings: NextPage = () => {
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
const [isUpdating, setIsUpdating] = useState(false);
@ -87,8 +83,7 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
return (
<>
<AppLayout
memberType={{ isMember, isOwner, isViewer, isGuest }}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -98,7 +93,6 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Estimates Settings" />
</Breadcrumbs>
}
settingsLayout
>
<CreateUpdateEstimateModal
isCreate={estimateToUpdate ? true : false}
@ -154,25 +148,9 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
</>
</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;

View File

@ -7,10 +7,8 @@ import useSWR, { mutate } from "swr";
// services
import projectService from "services/project.service";
import trackEventServices from "services/track-event.service";
// lib
import { requiredAdmin } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// hooks
import useToast from "hooks/use-toast";
// ui
@ -20,12 +18,12 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons";
import { DocumentTextIcon } from "@heroicons/react/24/outline";
// types
import { IProject, UserAuth } from "types";
import type { NextPage, GetServerSidePropsContext } from "next";
import { IProject } from "types";
import type { NextPage } from "next";
// fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
const FeaturesSettings: NextPage<UserAuth> = (props) => {
const FeaturesSettings: NextPage = () => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -79,8 +77,7 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
};
return (
<AppLayout
memberType={props}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -90,7 +87,6 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Features Settings" />
</Breadcrumbs>
}
settingsLayout
>
<section className="space-y-8">
<h3 className="text-2xl font-semibold">Features</h3>
@ -269,24 +265,8 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
</a>
</div>
</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;

View File

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

View File

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

View File

@ -10,7 +10,7 @@ import issuesService from "services/issues.service";
// lib
import { requiredAdmin } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import {
CreateUpdateLabelInline,
@ -29,9 +29,7 @@ import type { GetServerSidePropsContext, NextPage } from "next";
// fetch-keys
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
const LabelsSettings: NextPage<UserAuth> = (props) => {
const { isMember, isOwner, isViewer, isGuest } = props;
const LabelsSettings: NextPage = () => {
// create/edit label form
const [labelForm, setLabelForm] = useState(false);
@ -99,8 +97,7 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
handleClose={() => setLabelsListModal(false)}
parent={parentLabel}
/>
<AppLayout
memberType={{ isMember, isOwner, isViewer, isGuest }}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -110,7 +107,6 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Labels Settings" />
</Breadcrumbs>
}
settingsLayout
>
<section className="grid grid-cols-12 gap-10">
<div className="col-span-12 sm:col-span-5">
@ -182,25 +178,9 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
</>
</div>
</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;

View File

@ -8,12 +8,10 @@ import useSWR from "swr";
// services
import projectService from "services/project.service";
import workspaceService from "services/workspace.service";
// lib
import { requiredAdmin } from "lib/auth";
// hooks
import useToast from "hooks/use-toast";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
@ -23,8 +21,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
// types
import type { NextPage, GetServerSidePropsContext } from "next";
import { UserAuth } from "types";
import type { NextPage } from "next";
// fetch-keys
import {
PROJECT_DETAILS,
@ -35,7 +32,7 @@ import {
// constants
import { ROLE } from "constants/workspace";
const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGuest }) => {
const MembersSettings: NextPage = () => {
const [inviteModal, setInviteModal] = useState(false);
const [selectedRemoveMember, setSelectedRemoveMember] = 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}
members={members}
/>
<AppLayout
memberType={{ isMember, isOwner, isViewer, isGuest }}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -159,7 +155,6 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
settingsLayout
>
<section className="space-y-8">
<div className="flex items-end justify-between gap-4">
@ -274,25 +269,9 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
</div>
)}
</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;

View File

@ -4,13 +4,11 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// lib
import { requiredAdmin } from "lib/auth";
// services
import stateService from "services/state.service";
import projectService from "services/project.service";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components
import {
CreateUpdateStateInline,
@ -26,14 +24,11 @@ import { PlusIcon } from "@heroicons/react/24/outline";
// helpers
import { getStatesList, orderStateGroups } from "helpers/state.helper";
// types
import { UserAuth } from "types";
import type { NextPage, GetServerSidePropsContext } from "next";
import type { NextPage } from "next";
// fetch-keys
import { PROJECT_DETAILS, STATE_LIST } from "constants/fetch-keys";
const StatesSettings: NextPage<UserAuth> = (props) => {
const { isMember, isOwner, isViewer, isGuest } = props;
const StatesSettings: NextPage = () => {
const [activeGroup, setActiveGroup] = useState<StateGroup>(null);
const [selectedState, setSelectedState] = 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}
onClose={() => setSelectDeleteState(null)}
/>
<AppLayout
memberType={{ isMember, isOwner, isViewer, isGuest }}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -75,7 +69,6 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="States Settings" />
</Breadcrumbs>
}
settingsLayout
>
<div className="grid grid-cols-12 gap-10">
<div className="col-span-12 sm:col-span-5">
@ -149,25 +142,9 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
)}
</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;

View File

@ -1,30 +1,28 @@
import { useRouter } from "next/router";
import { GetServerSidePropsContext } from "next";
import useSWR from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import projectService from "services/project.service";
import viewsService from "services/views.service";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// components
import { IssuesFilterView, IssuesView } from "components/core";
// ui
import { CustomMenu, PrimaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types
import { UserAuth } from "types";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
import { StackedLayersIcon } from "components/icons";
// helpers
import { truncateText } from "helpers/string.helper";
// 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 { workspaceSlug, projectId, viewId } = router.query;
@ -56,8 +54,7 @@ const SingleView: React.FC<UserAuth> = (props) => {
return (
<IssueViewContextProvider>
<AppLayout
memberType={props}
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -104,39 +101,10 @@ const SingleView: React.FC<UserAuth> = (props) => {
</div>
}
>
<IssuesView userAuth={props} />
</AppLayout>
<IssuesView />
</ProjectAuthorizationWrapper>
</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;

View File

@ -4,40 +4,32 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import viewsService from "services/views.service";
import projectService from "services/project.service";
// layouts
import AppLayout from "layouts/app-layout";
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
//icons
import { PlusIcon } from "components/icons";
// image
// images
import emptyView from "public/empty-state/empty-view.svg";
// fetching keys
import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys";
// components
import { PrimaryButton, Loader, EmptyState } from "components/ui";
import { DeleteViewModal, CreateUpdateViewModal, SingleViewItem } from "components/views";
// types
import { IView, UserAuth } from "types";
import type { NextPage, GetServerSidePropsContext } from "next";
import { IView } from "types";
import type { NextPage } from "next";
const ProjectViews: NextPage<UserAuth> = (props) => {
const ProjectViews: NextPage = () => {
const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false);
const [selectedView, setSelectedView] = useState<IView | null>(null);
const {
query: { workspaceSlug, projectId },
} = useRouter();
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
@ -54,11 +46,10 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
);
return (
<AppLayout
<ProjectAuthorizationWrapper
meta={{
title: "Plane - Views",
}}
memberType={props}
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
@ -116,37 +107,8 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
<Loader.Item height="30px" />
</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;

View File

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

View File

@ -4,28 +4,19 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// services
import workspaceService from "services/workspace.service";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// ui
import { SecondaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types
import type { NextPage, GetServerSideProps } from "next";
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
type TBillingSettingsProps = {
isOwner: boolean;
isMember: boolean;
isViewer: boolean;
isGuest: boolean;
};
const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
const BillingSettings: NextPage = () => {
const {
query: { workspaceSlug },
} = useRouter();
@ -36,72 +27,44 @@ const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
);
return (
<>
<AppLayout
memberType={props}
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${activeWorkspace?.name ?? "Workspace"}`}
link={`/${workspaceSlug}`}
/>
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
settingsLayout
>
<section className="space-y-8">
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${activeWorkspace?.name ?? "Workspace"}`}
link={`/${workspaceSlug}`}
/>
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
>
<section className="space-y-8">
<div>
<h3 className="text-3xl font-bold leading-6 text-gray-900">Billing & Plans</h3>
<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>
<h3 className="text-3xl font-bold leading-6 text-gray-900">Billing & Plans</h3>
<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 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 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>
</section>
</AppLayout>
</>
<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>
</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;

View File

@ -1,58 +1,30 @@
import { useRouter } from "next/router";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import IntegrationGuide from "components/integration/guide";
// ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types
import { UserAuth } from "types";
import type { GetServerSideProps, NextPage } from "next";
import type { NextPage } from "next";
const ImportExport: NextPage<UserAuth> = (props) => {
const ImportExport: NextPage = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
return (
<AppLayout
memberType={props}
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title={`${workspaceSlug ?? "Workspace"}`} link={`/${workspaceSlug}`} />
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
settingsLayout
>
<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;

View File

@ -7,29 +7,26 @@ import useSWR, { mutate } from "swr";
// 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
import workspaceService from "services/workspace.service";
import fileService from "services/file.service";
// hooks
import useToast from "hooks/use-toast";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import { ImageUploadModal } from "components/core";
import { DeleteWorkspaceModal } from "components/workspace";
// ui
import { Spinner, Input, CustomSelect, OutlineButton, SecondaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { LinkIcon } from "@heroicons/react/24/outline";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
import type { IWorkspace, UserAuth } from "types";
import type { GetServerSideProps, NextPage } from "next";
import type { IWorkspace } from "types";
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS, USER_WORKSPACES } from "constants/fetch-keys";
// constants
@ -42,7 +39,7 @@ const defaultValues: Partial<IWorkspace> = {
logo: null,
};
const WorkspaceSettings: NextPage<UserAuth> = (props) => {
const WorkspaceSettings: NextPage = () => {
const [isOpen, setIsOpen] = useState(false);
const [isImageUploading, setIsImageUploading] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
@ -107,8 +104,7 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
};
return (
<AppLayout
memberType={props}
<WorkspaceAuthorizationLayout
meta={{
title: "Plane - Workspace Settings",
}}
@ -117,7 +113,6 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Settings`} />
</Breadcrumbs>
}
settingsLayout
>
<ImageUploadModal
isOpen={isImageUploadModalOpen}
@ -282,32 +277,8 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
<Spinner />
</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;

View File

@ -7,22 +7,19 @@ import useSWR from "swr";
// services
import workspaceService from "services/workspace.service";
import IntegrationService from "services/integration";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
// componentss
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import OAuthPopUp from "components/popup";
// ui
import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types
import type { NextPage, GetServerSideProps } from "next";
import { UserAuth } from "types";
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys";
const WorkspaceIntegrations: NextPage<UserAuth> = (props) => {
const WorkspaceIntegrations: NextPage = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
@ -36,66 +33,38 @@ const WorkspaceIntegrations: NextPage<UserAuth> = (props) => {
);
return (
<>
<AppLayout
memberType={props}
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${activeWorkspace?.name ?? "Workspace"}`}
link={`/${workspaceSlug}`}
/>
<BreadcrumbItem title="Integrations" />
</Breadcrumbs>
}
settingsLayout
>
<section className="space-y-8">
<h3 className="text-2xl font-semibold">Integrations</h3>
<div className="space-y-5">
{appIntegrations ? (
appIntegrations.map((integration) => (
<OAuthPopUp
key={integration.id}
workspaceSlug={workspaceSlug}
integration={integration}
/>
))
) : (
<Loader className="space-y-5">
<Loader.Item height="60px" />
<Loader.Item height="60px" />
</Loader>
)}
</div>
</section>
</AppLayout>
</>
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${activeWorkspace?.name ?? "Workspace"}`}
link={`/${workspaceSlug}`}
/>
<BreadcrumbItem title="Integrations" />
</Breadcrumbs>
}
>
<section className="space-y-8">
<h3 className="text-2xl font-semibold">Integrations</h3>
<div className="space-y-5">
{appIntegrations ? (
appIntegrations.map((integration) => (
<OAuthPopUp
key={integration.id}
workspaceSlug={workspaceSlug}
integration={integration}
/>
))
) : (
<Loader className="space-y-5">
<Loader.Item height="60px" />
<Loader.Item height="60px" />
</Loader>
)}
</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 WorkspaceIntegrations;

View File

@ -5,14 +5,12 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// lib
import { requiredWorkspaceAdmin } from "lib/auth";
// hooks
import useToast from "hooks/use-toast";
// services
import workspaceService from "services/workspace.service";
// layouts
import AppLayout from "layouts/app-layout";
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// components
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
@ -22,14 +20,13 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// types
import type { GetServerSideProps, NextPage } from "next";
import { UserAuth } from "types";
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
// constants
import { ROLE } from "constants/workspace";
const MembersSettings: NextPage<UserAuth> = (props) => {
const MembersSettings: NextPage = () => {
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null);
const [inviteModal, setInviteModal] = useState(false);
@ -129,8 +126,7 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
workspace_slug={workspaceSlug as string}
members={members}
/>
<AppLayout
memberType={props}
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
@ -140,7 +136,6 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
<BreadcrumbItem title="Members Settings" />
</Breadcrumbs>
}
settingsLayout
>
<section className="space-y-8">
<div className="flex items-end justify-between gap-4">
@ -254,33 +249,9 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
</div>
)}
</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;

View File

@ -42,7 +42,6 @@ const AppPostInstallation = ({
};
export async function getServerSideProps(context: any) {
console.log(context.query);
return {
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/`)
.then((response) => response?.data)
.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/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
throw error?.response;
});
}