mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
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:
parent
beedd57ee1
commit
3947a86fa7
3
apps/app/components/auth-screens/index.ts
Normal file
3
apps/app/components/auth-screens/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./project";
|
||||||
|
export * from "./workspace";
|
||||||
|
export * from "./not-authorized-view";
|
@ -7,16 +7,16 @@ import { useRouter } from "next/router";
|
|||||||
import DefaultLayout from "layouts/default-layout";
|
import DefaultLayout from "layouts/default-layout";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// img
|
// images
|
||||||
import ProjectSettingImg from "public/project-setting.svg";
|
import ProjectNotAuthorizedImg from "public/auth/project-not-authorized.svg";
|
||||||
|
import WorkspaceNotAuthorizedImg from "public/auth/workspace-not-authorized.svg";
|
||||||
|
|
||||||
type TNotAuthorizedViewProps = {
|
type Props = {
|
||||||
actionButton?: React.ReactNode;
|
actionButton?: React.ReactNode;
|
||||||
|
type: "project" | "workspace";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => {
|
export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
||||||
const { actionButton } = props;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { asPath: currentPath } = useRouter();
|
const { asPath: currentPath } = useRouter();
|
||||||
|
|
||||||
@ -29,7 +29,12 @@ export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
|
||||||
<div className="h-44 w-72">
|
<div className="h-44 w-72">
|
||||||
<Image src={ProjectSettingImg} height="176" width="288" alt="ProjectSettingImg" />
|
<Image
|
||||||
|
src={type === "project" ? ProjectNotAuthorizedImg : WorkspaceNotAuthorizedImg}
|
||||||
|
height="176"
|
||||||
|
width="288"
|
||||||
|
alt="ProjectSettingImg"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-xl font-medium text-gray-900">
|
<h1 className="text-xl font-medium text-gray-900">
|
||||||
Oops! You are not authorized to view this page
|
Oops! You are not authorized to view this page
|
||||||
@ -38,7 +43,7 @@ export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => {
|
|||||||
<div className="w-full text-base text-gray-500 max-w-md ">
|
<div className="w-full text-base text-gray-500 max-w-md ">
|
||||||
{user ? (
|
{user ? (
|
||||||
<p className="">
|
<p className="">
|
||||||
You have signed in as {user.email}.{" "}
|
You have signed in as {user.email}. <br />
|
||||||
<Link href={`/signin?next=${currentPath}`}>
|
<Link href={`/signin?next=${currentPath}`}>
|
||||||
<a className="text-gray-900 font-medium">Sign in</a>
|
<a className="text-gray-900 font-medium">Sign in</a>
|
||||||
</Link>{" "}
|
</Link>{" "}
|
1
apps/app/components/auth-screens/project/index.ts
Normal file
1
apps/app/components/auth-screens/project/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./join-project";
|
64
apps/app/components/auth-screens/project/join-project.tsx
Normal file
64
apps/app/components/auth-screens/project/join-project.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
1
apps/app/components/auth-screens/workspace/index.ts
Normal file
1
apps/app/components/auth-screens/workspace/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./not-a-member";
|
46
apps/app/components/auth-screens/workspace/not-a-member.tsx
Normal file
46
apps/app/components/auth-screens/workspace/not-a-member.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -8,7 +8,6 @@ export * from "./image-upload-modal";
|
|||||||
export * from "./issues-view-filter";
|
export * from "./issues-view-filter";
|
||||||
export * from "./issues-view";
|
export * from "./issues-view";
|
||||||
export * from "./link-modal";
|
export * from "./link-modal";
|
||||||
export * from "./not-authorized-view";
|
|
||||||
export * from "./image-picker-popover";
|
export * from "./image-picker-popover";
|
||||||
export * from "./filter-list";
|
export * from "./filter-list";
|
||||||
export * from "./feeds";
|
export * from "./feeds";
|
||||||
|
@ -11,6 +11,8 @@ import issuesService from "services/issues.service";
|
|||||||
import stateService from "services/state.service";
|
import stateService from "services/state.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track-event.service";
|
||||||
|
// contexts
|
||||||
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
@ -49,14 +51,12 @@ type Props = {
|
|||||||
type?: "issue" | "cycle" | "module";
|
type?: "issue" | "cycle" | "module";
|
||||||
openIssuesListModal?: () => void;
|
openIssuesListModal?: () => void;
|
||||||
isCompleted?: boolean;
|
isCompleted?: boolean;
|
||||||
userAuth: UserAuth;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssuesView: React.FC<Props> = ({
|
export const IssuesView: React.FC<Props> = ({
|
||||||
type = "issue",
|
type = "issue",
|
||||||
openIssuesListModal,
|
openIssuesListModal,
|
||||||
isCompleted = false,
|
isCompleted = false,
|
||||||
userAuth,
|
|
||||||
}) => {
|
}) => {
|
||||||
// create issue modal
|
// create issue modal
|
||||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||||
@ -84,6 +84,8 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
|
|
||||||
|
const { memberRole } = useProjectMyMembership();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -494,7 +496,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
isCompleted={isCompleted}
|
isCompleted={isCompleted}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
) : issueView === "kanban" ? (
|
) : issueView === "kanban" ? (
|
||||||
<AllBoards
|
<AllBoards
|
||||||
@ -514,7 +516,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
isCompleted={isCompleted}
|
isCompleted={isCompleted}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CalendarView />
|
<CalendarView />
|
||||||
|
@ -4,6 +4,8 @@ import dynamic from "next/dynamic";
|
|||||||
|
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
// contexts
|
||||||
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
// components
|
// components
|
||||||
import { Loader, TextArea } from "components/ui";
|
import { Loader, TextArea } from "components/ui";
|
||||||
const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), {
|
const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), {
|
||||||
@ -15,7 +17,7 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
// types
|
// types
|
||||||
import { IIssue, UserAuth } from "types";
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IssueDescriptionFormValues {
|
export interface IssueDescriptionFormValues {
|
||||||
name: string;
|
name: string;
|
||||||
@ -26,17 +28,14 @@ export interface IssueDescriptionFormValues {
|
|||||||
export interface IssueDetailsProps {
|
export interface IssueDetailsProps {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
handleFormSubmit: (value: IssueDescriptionFormValues) => Promise<void>;
|
handleFormSubmit: (value: IssueDescriptionFormValues) => Promise<void>;
|
||||||
userAuth: UserAuth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
export const IssueDescriptionForm: FC<IssueDetailsProps> = ({ issue, handleFormSubmit }) => {
|
||||||
issue,
|
|
||||||
handleFormSubmit,
|
|
||||||
userAuth,
|
|
||||||
}) => {
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [characterLimit, setCharacterLimit] = useState(false);
|
const [characterLimit, setCharacterLimit] = useState(false);
|
||||||
|
|
||||||
|
const { memberRole } = useProjectMyMembership();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
watch,
|
watch,
|
||||||
@ -86,7 +85,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
|||||||
reset(issue);
|
reset(issue);
|
||||||
}, [issue, reset]);
|
}, [issue, reset]);
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
@ -15,6 +15,8 @@ import useToast from "hooks/use-toast";
|
|||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
|
// contexts
|
||||||
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
// components
|
// components
|
||||||
import { LinkModal, LinksList } from "components/core";
|
import { LinkModal, LinksList } from "components/core";
|
||||||
import {
|
import {
|
||||||
@ -45,7 +47,7 @@ import {
|
|||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import type { ICycle, IIssue, IIssueLabels, IIssueLink, IModule, UserAuth } from "types";
|
import type { ICycle, IIssue, IIssueLabels, IIssueLink, IModule } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -54,7 +56,6 @@ type Props = {
|
|||||||
submitChanges: (formData: Partial<IIssue>) => void;
|
submitChanges: (formData: Partial<IIssue>) => void;
|
||||||
issueDetail: IIssue | undefined;
|
issueDetail: IIssue | undefined;
|
||||||
watch: UseFormWatch<IIssue>;
|
watch: UseFormWatch<IIssue>;
|
||||||
userAuth: UserAuth;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<IIssueLabels> = {
|
const defaultValues: Partial<IIssueLabels> = {
|
||||||
@ -67,7 +68,6 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
submitChanges,
|
submitChanges,
|
||||||
issueDetail,
|
issueDetail,
|
||||||
watch: watchIssue,
|
watch: watchIssue,
|
||||||
userAuth,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [createLabelForm, setCreateLabelForm] = useState(false);
|
const [createLabelForm, setCreateLabelForm] = useState(false);
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
@ -76,6 +76,8 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
|
const { memberRole } = useProjectMyMembership();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: issues } = useSWR(
|
const { data: issues } = useSWR(
|
||||||
@ -213,7 +215,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
reset();
|
reset();
|
||||||
}, [createLabelForm, reset]);
|
}, [createLabelForm, reset]);
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -260,7 +262,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarStateSelect
|
<SidebarStateSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: string) => submitChanges({ state: val })}
|
onChange={(val: string) => submitChanges({ state: val })}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -271,7 +273,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarAssigneeSelect
|
<SidebarAssigneeSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: string[]) => submitChanges({ assignees_list: val })}
|
onChange={(val: string[]) => submitChanges({ assignees_list: val })}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -282,7 +284,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarPrioritySelect
|
<SidebarPrioritySelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: string) => submitChanges({ priority: val })}
|
onChange={(val: string) => submitChanges({ priority: val })}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -293,7 +295,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarEstimateSelect
|
<SidebarEstimateSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: number) => submitChanges({ estimate_point: val })}
|
onChange={(val: number) => submitChanges({ estimate_point: val })}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -327,19 +329,19 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
<SidebarBlockerSelect
|
<SidebarBlockerSelect
|
||||||
submitChanges={submitChanges}
|
submitChanges={submitChanges}
|
||||||
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
|
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
<SidebarBlockedSelect
|
<SidebarBlockedSelect
|
||||||
submitChanges={submitChanges}
|
submitChanges={submitChanges}
|
||||||
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
|
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-wrap items-center py-2">
|
<div className="flex flex-wrap items-center py-2">
|
||||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||||
@ -369,12 +371,12 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarCycleSelect
|
<SidebarCycleSelect
|
||||||
issueDetail={issueDetail}
|
issueDetail={issueDetail}
|
||||||
handleCycleChange={handleCycleChange}
|
handleCycleChange={handleCycleChange}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
<SidebarModuleSelect
|
<SidebarModuleSelect
|
||||||
issueDetail={issueDetail}
|
issueDetail={issueDetail}
|
||||||
handleModuleChange={handleModuleChange}
|
handleModuleChange={handleModuleChange}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -637,7 +639,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<LinksList
|
<LinksList
|
||||||
links={issueDetail.issue_link}
|
links={issueDetail.issue_link}
|
||||||
handleDeleteLink={handleDeleteLink}
|
handleDeleteLink={handleDeleteLink}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
|
// contexts
|
||||||
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
import { CreateUpdateIssueModal } from "components/issues";
|
import { CreateUpdateIssueModal } from "components/issues";
|
||||||
@ -19,16 +21,15 @@ import { ChevronRightIcon, PlusIcon, XMarkIcon } from "@heroicons/react/24/outli
|
|||||||
// helpers
|
// helpers
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue, UserAuth } from "types";
|
import { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
parentIssue: IIssue;
|
parentIssue: IIssue;
|
||||||
userAuth: UserAuth;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
|
export const SubIssuesList: FC<Props> = ({ parentIssue }) => {
|
||||||
// states
|
// states
|
||||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||||
const [subIssuesListModal, setSubIssuesListModal] = useState(false);
|
const [subIssuesListModal, setSubIssuesListModal] = useState(false);
|
||||||
@ -37,6 +38,8 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
|
const { memberRole } = useProjectMyMembership();
|
||||||
|
|
||||||
const { data: subIssues } = useSWR<IIssue[] | undefined>(
|
const { data: subIssues } = useSWR<IIssue[] | undefined>(
|
||||||
workspaceSlug && projectId && issueId ? SUB_ISSUES(issueId as string) : null,
|
workspaceSlug && projectId && issueId ? SUB_ISSUES(issueId as string) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
@ -143,7 +146,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -21,6 +21,8 @@ import { Disclosure, Popover, Transition } from "@headlessui/react";
|
|||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
// services
|
// services
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
|
// contexts
|
||||||
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
@ -30,17 +32,16 @@ import ProgressChart from "components/core/sidebar/progress-chart";
|
|||||||
import { CustomMenu, CustomSelect, Loader, ProgressBar } from "components/ui";
|
import { CustomMenu, CustomSelect, Loader, ProgressBar } from "components/ui";
|
||||||
// icon
|
// icon
|
||||||
import { ExclamationIcon } from "components/icons";
|
import { ExclamationIcon } from "components/icons";
|
||||||
|
import { LinkIcon } from "@heroicons/react/20/solid";
|
||||||
// helpers
|
// helpers
|
||||||
import { isDateRangeValid, renderDateFormat, renderShortDate } from "helpers/date-time.helper";
|
import { renderDateFormat, renderShortDate } from "helpers/date-time.helper";
|
||||||
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
|
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
|
||||||
import { groupBy } from "helpers/array.helper";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue, IModule, ModuleLink, UserAuth } from "types";
|
import { IIssue, IModule, ModuleLink } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { MODULE_DETAILS } from "constants/fetch-keys";
|
import { MODULE_DETAILS } from "constants/fetch-keys";
|
||||||
// constant
|
// constant
|
||||||
import { MODULE_STATUS } from "constants/module";
|
import { MODULE_STATUS } from "constants/module";
|
||||||
import { LinkIcon } from "@heroicons/react/20/solid";
|
|
||||||
|
|
||||||
const defaultValues: Partial<IModule> = {
|
const defaultValues: Partial<IModule> = {
|
||||||
lead: "",
|
lead: "",
|
||||||
@ -55,22 +56,17 @@ type Props = {
|
|||||||
module?: IModule;
|
module?: IModule;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
moduleIssues?: IIssue[];
|
moduleIssues?: IIssue[];
|
||||||
userAuth: UserAuth;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModuleDetailsSidebar: React.FC<Props> = ({
|
export const ModuleDetailsSidebar: React.FC<Props> = ({ issues, module, isOpen, moduleIssues }) => {
|
||||||
issues,
|
|
||||||
module,
|
|
||||||
isOpen,
|
|
||||||
moduleIssues,
|
|
||||||
userAuth,
|
|
||||||
}) => {
|
|
||||||
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
||||||
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
|
|
||||||
|
const { memberRole } = useProjectMyMembership();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { reset, watch, control } = useForm({
|
const { reset, watch, control } = useForm({
|
||||||
@ -516,7 +512,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
completed: module.completed_issues,
|
completed: module.completed_issues,
|
||||||
cancelled: module.cancelled_issues,
|
cancelled: module.cancelled_issues,
|
||||||
}}
|
}}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
module={module}
|
module={module}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -542,11 +538,11 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 space-y-2 hover:bg-gray-100">
|
<div className="mt-2 space-y-2 hover:bg-gray-100">
|
||||||
{userAuth && module.link_module && module.link_module.length > 0 ? (
|
{memberRole && module.link_module && module.link_module.length > 0 ? (
|
||||||
<LinksList
|
<LinksList
|
||||||
links={module.link_module}
|
links={module.link_module}
|
||||||
handleDeleteLink={handleDeleteLink}
|
handleDeleteLink={handleDeleteLink}
|
||||||
userAuth={userAuth}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -147,7 +147,6 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: remove this and authorize using getServerSideProps
|
|
||||||
if (myWorkspaceMembership && isOpen) {
|
if (myWorkspaceMembership && isOpen) {
|
||||||
if (myWorkspaceMembership.role <= 10) return <IsGuestCondition setIsOpen={setIsOpen} />;
|
if (myWorkspaceMembership.role <= 10) return <IsGuestCondition setIsOpen={setIsOpen} />;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export * from "./create-project-modal";
|
export * from "./create-project-modal";
|
||||||
export * from "./delete-project-modal";
|
export * from "./delete-project-modal";
|
||||||
export * from "./join-project";
|
|
||||||
export * from "./sidebar-list";
|
export * from "./sidebar-list";
|
||||||
export * from "./single-integration-card";
|
export * from "./single-integration-card";
|
||||||
export * from "./single-project-card";
|
export * from "./single-project-card";
|
||||||
|
@ -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>
|
|
||||||
);
|
|
73
apps/app/contexts/project-member.context.tsx
Normal file
73
apps/app/contexts/project-member.context.tsx
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -12,9 +12,8 @@ import { PrimaryButton, Spinner } from "components/ui";
|
|||||||
// icon
|
// icon
|
||||||
import { LayerDiagonalIcon } from "components/icons";
|
import { LayerDiagonalIcon } from "components/icons";
|
||||||
// components
|
// components
|
||||||
import { NotAuthorizedView } from "components/core";
|
import { NotAuthorizedView, JoinProject } from "components/auth-screens";
|
||||||
import { CommandPalette } from "components/command-palette";
|
import { CommandPalette } from "components/command-palette";
|
||||||
import { JoinProject } from "components/project";
|
|
||||||
// local components
|
// local components
|
||||||
import Container from "layouts/container";
|
import Container from "layouts/container";
|
||||||
import AppSidebar from "layouts/app-layout/app-sidebar";
|
import AppSidebar from "layouts/app-layout/app-sidebar";
|
||||||
@ -61,7 +60,6 @@ const AppLayout: FC<AppLayoutProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// states
|
// states
|
||||||
const [toggleSidebar, setToggleSidebar] = useState(false);
|
const [toggleSidebar, setToggleSidebar] = useState(false);
|
||||||
const [isJoiningProject, setIsJoiningProject] = useState(false);
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
@ -83,21 +81,6 @@ const AppLayout: FC<AppLayoutProps> = ({
|
|||||||
memberType?.isViewer ||
|
memberType?.isViewer ||
|
||||||
memberType?.isGuest;
|
memberType?.isGuest;
|
||||||
|
|
||||||
const handleJoin = () => {
|
|
||||||
setIsJoiningProject(true);
|
|
||||||
projectService
|
|
||||||
.joinProject(workspaceSlug as string, {
|
|
||||||
project_ids: [projectId as string],
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
setIsJoiningProject(false);
|
|
||||||
projectMembersMutate();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container meta={meta}>
|
<Container meta={meta}>
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
@ -123,6 +106,7 @@ const AppLayout: FC<AppLayoutProps> = ({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
type="project"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<main className="flex h-screen w-full min-w-0 flex-col overflow-y-auto">
|
<main className="flex h-screen w-full min-w-0 flex-col overflow-y-auto">
|
||||||
@ -170,7 +154,7 @@ const AppLayout: FC<AppLayoutProps> = ({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<JoinProject isJoiningProject={isJoiningProject} handleJoin={handleJoin} />
|
<JoinProject />
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
)}
|
)}
|
||||||
|
2
apps/app/layouts/auth-layout/index.ts
Normal file
2
apps/app/layouts/auth-layout/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./project-authorization-wrapper";
|
||||||
|
export * from "./workspace-authorization-wrapper";
|
141
apps/app/layouts/auth-layout/project-authorization-wrapper.tsx
Normal file
141
apps/app/layouts/auth-layout/project-authorization-wrapper.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
158
apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx
Normal file
158
apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -2,7 +2,7 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
profilePage: boolean;
|
profilePage?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsNavbar: React.FC<Props> = ({ profilePage = false }) => {
|
const SettingsNavbar: React.FC<Props> = ({ profilePage = false }) => {
|
||||||
|
@ -4,10 +4,8 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredAuth } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// services
|
// services
|
||||||
import userService from "services/user.service";
|
import userService from "services/user.service";
|
||||||
// components
|
// components
|
||||||
@ -18,7 +16,7 @@ import {
|
|||||||
IssuesStats,
|
IssuesStats,
|
||||||
} from "components/workspace";
|
} from "components/workspace";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_WORKSPACE_DASHBOARD } from "constants/fetch-keys";
|
import { USER_WORKSPACE_DASHBOARD } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -38,14 +36,16 @@ const WorkspacePage: NextPage = () => {
|
|||||||
}, [month, workspaceSlug]);
|
}, [month, workspaceSlug]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout noHeader={true}>
|
<WorkspaceAuthorizationLayout noHeader>
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
<div
|
<div
|
||||||
className="flex flex-col bg-white justify-between gap-x-2 gap-y-6 rounded-lg px-8 py-6 text-black md:flex-row md:items-center md:py-3"
|
className="flex flex-col bg-white justify-between gap-x-2 gap-y-6 rounded-lg px-8 py-6 text-black md:flex-row md:items-center md:py-3"
|
||||||
// style={{ background: "linear-gradient(90deg, #8e2de2 0%, #4a00e0 100%)" }}
|
// style={{ background: "linear-gradient(90deg, #8e2de2 0%, #4a00e0 100%)" }}
|
||||||
>
|
>
|
||||||
<p className="font-semibold">Plane is a open source application, to support us you can star us on GitHub!</p>
|
<p className="font-semibold">
|
||||||
|
Plane is a open source application, to support us you can star us on GitHub!
|
||||||
|
</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* <a href="#" target="_blank" rel="noopener noreferrer">
|
{/* <a href="#" target="_blank" rel="noopener noreferrer">
|
||||||
View roadmap
|
View roadmap
|
||||||
@ -73,29 +73,8 @@ const WorkspacePage: NextPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppLayout>
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
user,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkspacePage;
|
export default WorkspacePage;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// headless ui
|
// headless ui
|
||||||
@ -6,7 +7,7 @@ import { Disclosure, Popover, Transition } from "@headlessui/react";
|
|||||||
// icons
|
// icons
|
||||||
import { ChevronDownIcon, PlusIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
|
import { ChevronDownIcon, PlusIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssues from "hooks/use-issues";
|
import useIssues from "hooks/use-issues";
|
||||||
// ui
|
// ui
|
||||||
@ -36,166 +37,164 @@ const MyIssuesPage: NextPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<WorkspaceAuthorizationLayout
|
||||||
<AppLayout
|
breadcrumbs={
|
||||||
breadcrumbs={
|
<Breadcrumbs>
|
||||||
<Breadcrumbs>
|
<BreadcrumbItem title="My Issues" />
|
||||||
<BreadcrumbItem title="My Issues" />
|
</Breadcrumbs>
|
||||||
</Breadcrumbs>
|
}
|
||||||
}
|
right={
|
||||||
right={
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
{myIssues && myIssues.length > 0 && (
|
||||||
{myIssues && myIssues.length > 0 && (
|
<Popover className="relative">
|
||||||
<Popover className="relative">
|
{({ open }) => (
|
||||||
{({ open }) => (
|
<>
|
||||||
<>
|
<Popover.Button
|
||||||
<Popover.Button
|
className={`group flex items-center gap-2 rounded-md border bg-transparent p-2 text-xs font-medium hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${
|
||||||
className={`group flex items-center gap-2 rounded-md border bg-transparent p-2 text-xs font-medium hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${
|
open ? "bg-gray-100 text-gray-900" : "text-gray-500"
|
||||||
open ? "bg-gray-100 text-gray-900" : "text-gray-500"
|
}`}
|
||||||
}`}
|
>
|
||||||
>
|
<span>View</span>
|
||||||
<span>View</span>
|
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
|
||||||
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
|
</Popover.Button>
|
||||||
</Popover.Button>
|
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
enter="transition ease-out duration-200"
|
enter="transition ease-out duration-200"
|
||||||
enterFrom="opacity-0 translate-y-1"
|
enterFrom="opacity-0 translate-y-1"
|
||||||
enterTo="opacity-100 translate-y-0"
|
enterTo="opacity-100 translate-y-0"
|
||||||
leave="transition ease-in duration-150"
|
leave="transition ease-in duration-150"
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute right-1/2 z-10 mr-5 mt-1 w-screen max-w-xs translate-x-1/2 transform overflow-hidden rounded-lg bg-white p-3 shadow-lg">
|
<Popover.Panel className="absolute right-1/2 z-10 mr-5 mt-1 w-screen max-w-xs translate-x-1/2 transform overflow-hidden rounded-lg bg-white p-3 shadow-lg">
|
||||||
<div className="relative flex flex-col gap-1 gap-y-4">
|
<div className="relative flex flex-col gap-1 gap-y-4">
|
||||||
<div className="relative flex flex-col gap-1">
|
<div className="relative flex flex-col gap-1">
|
||||||
<h4 className="text-base text-gray-600">Properties</h4>
|
<h4 className="text-base text-gray-600">Properties</h4>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{Object.keys(properties).map((key) => (
|
{Object.keys(properties).map((key) => (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
type="button"
|
type="button"
|
||||||
className={`rounded border border-theme px-2 py-1 text-xs capitalize ${
|
className={`rounded border border-theme px-2 py-1 text-xs capitalize ${
|
||||||
properties[key as keyof Properties]
|
properties[key as keyof Properties]
|
||||||
? "border-theme bg-theme text-white"
|
? "border-theme bg-theme text-white"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setProperties(key as keyof Properties)}
|
onClick={() => setProperties(key as keyof Properties)}
|
||||||
>
|
>
|
||||||
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
<PrimaryButton
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
onClick={() => {
|
|
||||||
const e = new KeyboardEvent("keydown", { key: "c" });
|
|
||||||
document.dispatchEvent(e);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlusIcon className="w-4 h-4" />
|
|
||||||
Add Issue
|
|
||||||
</PrimaryButton>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex h-full w-full flex-col space-y-5">
|
|
||||||
{myIssues ? (
|
|
||||||
<>
|
|
||||||
{myIssues.length > 0 ? (
|
|
||||||
<div className="flex flex-col space-y-5">
|
|
||||||
<Disclosure as="div" defaultOpen>
|
|
||||||
{({ open }) => (
|
|
||||||
<div className="rounded-[10px] border border-gray-300 bg-white">
|
|
||||||
<div
|
|
||||||
className={`flex items-center justify-start bg-gray-100 px-5 py-3 ${
|
|
||||||
open ? "rounded-t-[10px]" : "rounded-[10px]"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Disclosure.Button>
|
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
<span>
|
|
||||||
<ChevronDownIcon
|
|
||||||
className={`h-4 w-4 text-gray-500 ${
|
|
||||||
!open ? "-rotate-90 transform" : ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<h2 className="font-medium leading-5">My Issues</h2>
|
|
||||||
<span className="rounded-full bg-gray-200 py-0.5 px-3 text-sm text-black">
|
|
||||||
{myIssues.length}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Disclosure.Button>
|
|
||||||
</div>
|
|
||||||
<Transition
|
|
||||||
show={open}
|
|
||||||
enter="transition duration-100 ease-out"
|
|
||||||
enterFrom="transform opacity-0"
|
|
||||||
enterTo="transform opacity-100"
|
|
||||||
leave="transition duration-75 ease-out"
|
|
||||||
leaveFrom="transform opacity-100"
|
|
||||||
leaveTo="transform opacity-0"
|
|
||||||
>
|
|
||||||
<Disclosure.Panel>
|
|
||||||
{myIssues.map((issue: IIssue) => (
|
|
||||||
<MyIssuesListItem
|
|
||||||
key={issue.id}
|
|
||||||
issue={issue}
|
|
||||||
properties={properties}
|
|
||||||
projectId={issue.project}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Disclosure.Panel>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Popover.Panel>
|
||||||
</Disclosure>
|
</Transition>
|
||||||
</div>
|
</>
|
||||||
) : (
|
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center px-4">
|
|
||||||
<EmptySpace
|
|
||||||
title="You don't have any issue assigned to you yet."
|
|
||||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
|
||||||
Icon={RectangleStackIcon}
|
|
||||||
>
|
|
||||||
<EmptySpaceItem
|
|
||||||
title="Create a new issue"
|
|
||||||
description={
|
|
||||||
<span>
|
|
||||||
Use <pre className="inline rounded bg-gray-200 px-2 py-1">C</pre> shortcut
|
|
||||||
to create a new issue
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
Icon={PlusIcon}
|
|
||||||
action={() => {
|
|
||||||
const e = new KeyboardEvent("keydown", {
|
|
||||||
key: "c",
|
|
||||||
});
|
|
||||||
document.dispatchEvent(e);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EmptySpace>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</Popover>
|
||||||
) : (
|
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
<PrimaryButton
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", { key: "c" });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusIcon className="w-4 h-4" />
|
||||||
|
Add Issue
|
||||||
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</AppLayout>
|
}
|
||||||
</>
|
>
|
||||||
|
<div className="flex h-full w-full flex-col space-y-5">
|
||||||
|
{myIssues ? (
|
||||||
|
<>
|
||||||
|
{myIssues.length > 0 ? (
|
||||||
|
<div className="flex flex-col space-y-5">
|
||||||
|
<Disclosure as="div" defaultOpen>
|
||||||
|
{({ open }) => (
|
||||||
|
<div className="rounded-[10px] border border-gray-300 bg-white">
|
||||||
|
<div
|
||||||
|
className={`flex items-center justify-start bg-gray-100 px-5 py-3 ${
|
||||||
|
open ? "rounded-t-[10px]" : "rounded-[10px]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Disclosure.Button>
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<span>
|
||||||
|
<ChevronDownIcon
|
||||||
|
className={`h-4 w-4 text-gray-500 ${
|
||||||
|
!open ? "-rotate-90 transform" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<h2 className="font-medium leading-5">My Issues</h2>
|
||||||
|
<span className="rounded-full bg-gray-200 py-0.5 px-3 text-sm text-black">
|
||||||
|
{myIssues.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Disclosure.Button>
|
||||||
|
</div>
|
||||||
|
<Transition
|
||||||
|
show={open}
|
||||||
|
enter="transition duration-100 ease-out"
|
||||||
|
enterFrom="transform opacity-0"
|
||||||
|
enterTo="transform opacity-100"
|
||||||
|
leave="transition duration-75 ease-out"
|
||||||
|
leaveFrom="transform opacity-100"
|
||||||
|
leaveTo="transform opacity-0"
|
||||||
|
>
|
||||||
|
<Disclosure.Panel>
|
||||||
|
{myIssues.map((issue: IIssue) => (
|
||||||
|
<MyIssuesListItem
|
||||||
|
key={issue.id}
|
||||||
|
issue={issue}
|
||||||
|
properties={properties}
|
||||||
|
projectId={issue.project}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Disclosure>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full flex-col items-center justify-center px-4">
|
||||||
|
<EmptySpace
|
||||||
|
title="You don't have any issue assigned to you yet."
|
||||||
|
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||||
|
Icon={RectangleStackIcon}
|
||||||
|
>
|
||||||
|
<EmptySpaceItem
|
||||||
|
title="Create a new issue"
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
Use <pre className="inline rounded bg-gray-200 px-2 py-1">C</pre> shortcut
|
||||||
|
to create a new issue
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
Icon={PlusIcon}
|
||||||
|
action={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
key: "c",
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</EmptySpace>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import { GetServerSidePropsContext } from "next";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import userService from "services/user.service";
|
import userService from "services/user.service";
|
||||||
// lib
|
|
||||||
import { requiredAuth } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
|
// components
|
||||||
|
import { Feeds } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
import { Feeds } from "components/core";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_ACTIVITY } from "constants/fetch-keys";
|
import { USER_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -19,7 +16,7 @@ const ProfileActivity = () => {
|
|||||||
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
|
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<WorkspaceAuthorizationLayout
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - My Profile",
|
title: "Plane - My Profile",
|
||||||
}}
|
}}
|
||||||
@ -28,7 +25,6 @@ const ProfileActivity = () => {
|
|||||||
<BreadcrumbItem title="My Profile Activity" />
|
<BreadcrumbItem title="My Profile Activity" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
profilePage
|
profilePage
|
||||||
>
|
>
|
||||||
{userActivity ? (
|
{userActivity ? (
|
||||||
@ -43,29 +39,8 @@ const ProfileActivity = () => {
|
|||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
user,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProfileActivity;
|
export default ProfileActivity;
|
||||||
|
@ -4,8 +4,6 @@ import Image from "next/image";
|
|||||||
|
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// lib
|
|
||||||
import { requiredAuth } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import fileService from "services/file.service";
|
import fileService from "services/file.service";
|
||||||
import userService from "services/user.service";
|
import userService from "services/user.service";
|
||||||
@ -13,7 +11,7 @@ import userService from "services/user.service";
|
|||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import { ImageUploadModal } from "components/core";
|
import { ImageUploadModal } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
@ -22,7 +20,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// icons
|
// icons
|
||||||
import { UserIcon } from "@heroicons/react/24/outline";
|
import { UserIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
import type { IUser } from "types";
|
import type { IUser } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { USER_ROLES } from "constants/workspace";
|
import { USER_ROLES } from "constants/workspace";
|
||||||
@ -123,7 +121,7 @@ const Profile: NextPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<WorkspaceAuthorizationLayout
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - My Profile",
|
title: "Plane - My Profile",
|
||||||
}}
|
}}
|
||||||
@ -132,7 +130,6 @@ const Profile: NextPage = () => {
|
|||||||
<BreadcrumbItem title="My Profile" />
|
<BreadcrumbItem title="My Profile" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
profilePage
|
profilePage
|
||||||
>
|
>
|
||||||
<ImageUploadModal
|
<ImageUploadModal
|
||||||
@ -282,29 +279,8 @@ const Profile: NextPage = () => {
|
|||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
user,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Profile;
|
export default Profile;
|
||||||
|
@ -3,14 +3,11 @@ import React, { useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
import { GetServerSidePropsContext } from "next";
|
|
||||||
// icons
|
// icons
|
||||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||||
import { CyclesIcon } from "components/icons";
|
import { CyclesIcon } from "components/icons";
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// contexts
|
// contexts
|
||||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||||
// components
|
// components
|
||||||
@ -28,8 +25,6 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
import { getDateRangeStatus } from "helpers/date-time.helper";
|
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||||
// types
|
|
||||||
import { UserAuth } from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_ISSUES,
|
CYCLE_ISSUES,
|
||||||
@ -39,7 +34,7 @@ import {
|
|||||||
PROJECT_ISSUES_LIST,
|
PROJECT_ISSUES_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
const SingleCycle: React.FC<UserAuth> = (props) => {
|
const SingleCycle: React.FC = () => {
|
||||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||||
const [cycleSidebar, setCycleSidebar] = useState(true);
|
const [cycleSidebar, setCycleSidebar] = useState(true);
|
||||||
|
|
||||||
@ -117,8 +112,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||||||
issues={issues?.filter((i) => !i.cycle_id) ?? []}
|
issues={issues?.filter((i) => !i.cycle_id) ?? []}
|
||||||
handleOnSubmit={handleAddIssuesToCycle}
|
handleOnSubmit={handleAddIssuesToCycle}
|
||||||
/>
|
/>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -169,7 +163,6 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||||||
<div className={`h-full ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}>
|
<div className={`h-full ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}>
|
||||||
<IssuesView
|
<IssuesView
|
||||||
type="cycle"
|
type="cycle"
|
||||||
userAuth={props}
|
|
||||||
openIssuesListModal={openIssuesListModal}
|
openIssuesListModal={openIssuesListModal}
|
||||||
isCompleted={cycleStatus === "completed" ?? false}
|
isCompleted={cycleStatus === "completed" ?? false}
|
||||||
/>
|
/>
|
||||||
@ -180,38 +173,9 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||||||
isOpen={cycleSidebar}
|
isOpen={cycleSidebar}
|
||||||
isCompleted={cycleStatus === "completed" ?? false}
|
isCompleted={cycleStatus === "completed" ?? false}
|
||||||
/>
|
/>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</IssueViewContextProvider>
|
</IssueViewContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SingleCycle;
|
export default SingleCycle;
|
||||||
|
@ -5,27 +5,24 @@ import dynamic from "next/dynamic";
|
|||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
// headless ui
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import cycleService from "services/cycles.service";
|
import cycleService from "services/cycles.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import { CompletedCyclesListProps, CreateUpdateCycleModal, CyclesList } from "components/cycles";
|
import { CompletedCyclesListProps, CreateUpdateCycleModal, CyclesList } from "components/cycles";
|
||||||
// ui
|
// ui
|
||||||
import { Loader, PrimaryButton } from "components/ui";
|
import { Loader, PrimaryButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { SelectCycleType, UserAuth } from "types";
|
import { SelectCycleType } from "types";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetching keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
CYCLE_CURRENT_AND_UPCOMING_LIST,
|
||||||
CYCLE_DRAFT_LIST,
|
CYCLE_DRAFT_LIST,
|
||||||
@ -44,13 +41,12 @@ const CompletedCyclesList = dynamic<CompletedCyclesListProps>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const ProjectCycles: NextPage<UserAuth> = (props) => {
|
const ProjectCycles: NextPage = () => {
|
||||||
const [selectedCycle, setSelectedCycle] = useState<SelectCycleType>();
|
const [selectedCycle, setSelectedCycle] = useState<SelectCycleType>();
|
||||||
const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
|
const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
|
||||||
|
|
||||||
const {
|
const router = useRouter();
|
||||||
query: { workspaceSlug, projectId },
|
const { workspaceSlug, projectId } = router.query;
|
||||||
} = useRouter();
|
|
||||||
|
|
||||||
const { data: activeProject } = useSWR(
|
const { data: activeProject } = useSWR(
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||||
@ -82,11 +78,10 @@ const ProjectCycles: NextPage<UserAuth> = (props) => {
|
|||||||
}, [createUpdateCycleModal]);
|
}, [createUpdateCycleModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - Cycles",
|
title: "Plane - Cycles",
|
||||||
}}
|
}}
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
@ -195,37 +190,8 @@ const ProjectCycles: NextPage<UserAuth> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectCycles;
|
export default ProjectCycles;
|
||||||
|
@ -9,10 +9,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
IssueDescriptionForm,
|
IssueDescriptionForm,
|
||||||
@ -27,8 +25,8 @@ import {
|
|||||||
import { Loader, CustomMenu } from "components/ui";
|
import { Loader, CustomMenu } from "components/ui";
|
||||||
import { Breadcrumbs } from "components/breadcrumbs";
|
import { Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// types
|
||||||
import { IIssue, UserAuth } from "types";
|
import { IIssue } from "types";
|
||||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS, SUB_ISSUES } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS, SUB_ISSUES } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -47,7 +45,7 @@ const defaultValues = {
|
|||||||
labels_list: [],
|
labels_list: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
const IssueDetailsPage: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
@ -122,8 +120,7 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||||||
}, [issueDetails, reset, issueId]);
|
}, [issueDetails, reset, issueId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={props}
|
|
||||||
noPadding={true}
|
noPadding={true}
|
||||||
bg="secondary"
|
bg="secondary"
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
@ -191,20 +188,16 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<IssueDescriptionForm
|
<IssueDescriptionForm issue={issueDetails} handleFormSubmit={submitChanges} />
|
||||||
issue={issueDetails}
|
|
||||||
handleFormSubmit={submitChanges}
|
|
||||||
userAuth={props}
|
|
||||||
/>
|
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
<SubIssuesList parentIssue={issueDetails} userAuth={props} />
|
<SubIssuesList parentIssue={issueDetails} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3 py-3">
|
<div className="flex flex-col gap-3 py-3">
|
||||||
<h3 className="text-lg">Attachments</h3>
|
<h3 className="text-lg">Attachments</h3>
|
||||||
<div className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
<div className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||||
<IssueAttachmentUpload/>
|
<IssueAttachmentUpload />
|
||||||
<IssueAttachments />
|
<IssueAttachments />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-5 bg-secondary pt-3">
|
<div className="space-y-5 bg-secondary pt-3">
|
||||||
@ -219,7 +212,6 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||||||
issueDetail={issueDetails}
|
issueDetail={issueDetails}
|
||||||
submitChanges={submitChanges}
|
submitChanges={submitChanges}
|
||||||
watch={watch}
|
watch={watch}
|
||||||
userAuth={props}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -239,37 +231,8 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IssueDetailsPage;
|
export default IssueDetailsPage;
|
||||||
|
@ -2,12 +2,10 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// contexts
|
// contexts
|
||||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||||
// components
|
// components
|
||||||
@ -18,12 +16,11 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { UserAuth } from "types";
|
import type { NextPage } from "next";
|
||||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const ProjectIssues: NextPage<UserAuth> = (props) => {
|
const ProjectIssues: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -36,8 +33,7 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueViewContextProvider>
|
<IssueViewContextProvider>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
@ -60,39 +56,10 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IssuesView userAuth={props} />
|
<IssuesView />
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</IssueViewContextProvider>
|
</IssueViewContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectIssues;
|
export default ProjectIssues;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { GetServerSidePropsContext } from "next";
|
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
@ -12,15 +12,13 @@ import {
|
|||||||
RectangleGroupIcon,
|
RectangleGroupIcon,
|
||||||
RectangleStackIcon,
|
RectangleStackIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// contexts
|
// contexts
|
||||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||||
// components
|
// components
|
||||||
@ -32,7 +30,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IModule, UserAuth } from "types";
|
import { IModule } from "types";
|
||||||
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
@ -42,7 +40,7 @@ import {
|
|||||||
PROJECT_ISSUES_LIST,
|
PROJECT_ISSUES_LIST,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
const SingleModule: React.FC<UserAuth> = (props) => {
|
const SingleModule: React.FC = () => {
|
||||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||||
const [moduleSidebar, setModuleSidebar] = useState(true);
|
const [moduleSidebar, setModuleSidebar] = useState(true);
|
||||||
|
|
||||||
@ -121,8 +119,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||||||
issues={issues?.filter((i) => !i.module_id) ?? []}
|
issues={issues?.filter((i) => !i.module_id) ?? []}
|
||||||
handleOnSubmit={handleAddIssuesToModule}
|
handleOnSubmit={handleAddIssuesToModule}
|
||||||
/>
|
/>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -173,11 +170,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||||||
{moduleIssues ? (
|
{moduleIssues ? (
|
||||||
moduleIssues.length > 0 ? (
|
moduleIssues.length > 0 ? (
|
||||||
<div className={`h-full ${moduleSidebar ? "mr-[24rem]" : ""} duration-300`}>
|
<div className={`h-full ${moduleSidebar ? "mr-[24rem]" : ""} duration-300`}>
|
||||||
<IssuesView
|
<IssuesView type="module" openIssuesListModal={openIssuesListModal} />
|
||||||
type="module"
|
|
||||||
userAuth={props}
|
|
||||||
openIssuesListModal={openIssuesListModal}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
@ -220,40 +213,10 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||||||
module={moduleDetails}
|
module={moduleDetails}
|
||||||
isOpen={moduleSidebar}
|
isOpen={moduleSidebar}
|
||||||
moduleIssues={moduleIssues}
|
moduleIssues={moduleIssues}
|
||||||
userAuth={props}
|
|
||||||
/>
|
/>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</IssueViewContextProvider>
|
</IssueViewContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SingleModule;
|
export default SingleModule;
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
|
||||||
// image
|
|
||||||
import emptyModule from "public/empty-state/empty-module.svg";
|
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
@ -19,19 +15,22 @@ import { CreateUpdateModuleModal, SingleModuleCard } from "components/modules";
|
|||||||
import { EmptyState, Loader, PrimaryButton } from "components/ui";
|
import { EmptyState, Loader, PrimaryButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
// images
|
||||||
|
import emptyModule from "public/empty-state/empty-module.svg";
|
||||||
// types
|
// types
|
||||||
import { IModule, SelectModuleType } from "types/modules";
|
import { IModule, SelectModuleType } from "types/modules";
|
||||||
import { UserAuth } from "types";
|
import type { NextPage } from "next";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const ProjectModules: NextPage<UserAuth> = (props) => {
|
const ProjectModules: NextPage = () => {
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId } = router.query;
|
|
||||||
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
|
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
|
||||||
const [createUpdateModule, setCreateUpdateModule] = useState(false);
|
const [createUpdateModule, setCreateUpdateModule] = useState(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { data: activeProject } = useSWR(
|
const { data: activeProject } = useSWR(
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
@ -60,11 +59,10 @@ const ProjectModules: NextPage<UserAuth> = (props) => {
|
|||||||
}, [createUpdateModule]);
|
}, [createUpdateModule]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - Modules",
|
title: "Plane - Modules",
|
||||||
}}
|
}}
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
@ -124,37 +122,8 @@ const ProjectModules: NextPage<UserAuth> = (props) => {
|
|||||||
<Loader.Item height="100px" />
|
<Loader.Item height="100px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectModules;
|
export default ProjectModules;
|
||||||
|
@ -13,8 +13,6 @@ import { TwitterPicker } from "react-color";
|
|||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
||||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/pages.service";
|
||||||
@ -22,7 +20,7 @@ import issuesService from "services/issues.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages";
|
import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages";
|
||||||
// ui
|
// ui
|
||||||
@ -43,8 +41,8 @@ import { renderShortTime } from "helpers/date-time.helper";
|
|||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
import { IIssueLabels, IPage, IPageBlock, UserAuth } from "types";
|
import { IIssueLabels, IPage, IPageBlock } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
PAGE_BLOCKS_LIST,
|
PAGE_BLOCKS_LIST,
|
||||||
@ -53,7 +51,7 @@ import {
|
|||||||
PROJECT_ISSUE_LABELS,
|
PROJECT_ISSUE_LABELS,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
const SinglePage: NextPage<UserAuth> = (props) => {
|
const SinglePage: NextPage = () => {
|
||||||
const [createBlockForm, setCreateBlockForm] = useState(false);
|
const [createBlockForm, setCreateBlockForm] = useState(false);
|
||||||
|
|
||||||
const scrollToRef = useRef<HTMLDivElement>(null);
|
const scrollToRef = useRef<HTMLDivElement>(null);
|
||||||
@ -272,11 +270,10 @@ const SinglePage: NextPage<UserAuth> = (props) => {
|
|||||||
}, [reset, pageDetails]);
|
}, [reset, pageDetails]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - Pages",
|
title: "Plane - Pages",
|
||||||
}}
|
}}
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
@ -506,37 +503,8 @@ const SinglePage: NextPage<UserAuth> = (props) => {
|
|||||||
<Loader.Item height="200px" />
|
<Loader.Item height="200px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SinglePage;
|
export default SinglePage;
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
@ -20,16 +17,17 @@ import useToast from "hooks/use-toast";
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "components/icons";
|
import { PlusIcon } from "components/icons";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import { RecentPagesList, CreateUpdatePageModal, TPagesListProps } from "components/pages";
|
import { RecentPagesList, CreateUpdatePageModal, TPagesListProps } from "components/pages";
|
||||||
// ui
|
// ui
|
||||||
import { Input, PrimaryButton } from "components/ui";
|
import { Input, PrimaryButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { ListBulletIcon, RectangleGroupIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
|
import { ListBulletIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
|
||||||
// types
|
// types
|
||||||
import { IPage, TPageViewProps, UserAuth } from "types";
|
import { IPage, TPageViewProps } from "types";
|
||||||
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
ALL_PAGES_LIST,
|
ALL_PAGES_LIST,
|
||||||
@ -66,7 +64,7 @@ const OtherPagesList = dynamic<TPagesListProps>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const ProjectPages: NextPage<UserAuth> = (props) => {
|
const ProjectPages: NextPage = () => {
|
||||||
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
|
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
|
||||||
|
|
||||||
const [viewType, setViewType] = useState<TPageViewProps>("list");
|
const [viewType, setViewType] = useState<TPageViewProps>("list");
|
||||||
@ -153,11 +151,10 @@ const ProjectPages: NextPage<UserAuth> = (props) => {
|
|||||||
isOpen={createUpdatePageModal}
|
isOpen={createUpdatePageModal}
|
||||||
handleClose={() => setCreateUpdatePageModal(false)}
|
handleClose={() => setCreateUpdatePageModal(false)}
|
||||||
/>
|
/>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - Pages",
|
title: "Plane - Pages",
|
||||||
}}
|
}}
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
@ -266,38 +263,9 @@ const ProjectPages: NextPage<UserAuth> = (props) => {
|
|||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectPages;
|
export default ProjectPages;
|
||||||
|
@ -6,10 +6,8 @@ import Image from "next/image";
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// lib
|
|
||||||
import { requiredAdmin } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// hooks
|
// hooks
|
||||||
@ -19,30 +17,20 @@ import { CustomSelect, Loader, SecondaryButton } from "components/ui";
|
|||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// types
|
||||||
import { IProject, IWorkspace } from "types";
|
import { IProject, IWorkspace } from "types";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, PROJECT_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
|
||||||
type TControlSettingsProps = {
|
|
||||||
isMember: boolean;
|
|
||||||
isOwner: boolean;
|
|
||||||
isViewer: boolean;
|
|
||||||
isGuest: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultValues: Partial<IProject> = {
|
const defaultValues: Partial<IProject> = {
|
||||||
project_lead: null,
|
project_lead: null,
|
||||||
default_assignee: null,
|
default_assignee: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
const ControlSettings: NextPage = () => {
|
||||||
const { isMember, isOwner, isViewer, isGuest } = props;
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const {
|
const router = useRouter();
|
||||||
query: { workspaceSlug, projectId },
|
const { workspaceSlug, projectId } = router.query;
|
||||||
} = useRouter();
|
|
||||||
|
|
||||||
const { data: projectDetails } = useSWR<IProject>(
|
const { data: projectDetails } = useSWR<IProject>(
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||||
@ -102,9 +90,9 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -114,7 +102,6 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||||||
<BreadcrumbItem title="Control Settings" />
|
<BreadcrumbItem title="Control Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="space-y-8 sm:space-y-12">
|
<div className="space-y-8 sm:space-y-12">
|
||||||
@ -245,24 +232,8 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ControlSettings;
|
export default ControlSettings;
|
||||||
|
@ -8,29 +8,25 @@ import useSWR, { mutate } from "swr";
|
|||||||
import estimatesService from "services/estimates.service";
|
import estimatesService from "services/estimates.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredAdmin } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
|
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
|
||||||
|
|
||||||
//hooks
|
//hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { Loader, PrimaryButton } from "components/ui";
|
import { Loader } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IEstimate, UserAuth, IProject } from "types";
|
import { IEstimate, IProject } from "types";
|
||||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const EstimatesSettings: NextPage<UserAuth> = (props) => {
|
const EstimatesSettings: NextPage = () => {
|
||||||
const { isMember, isOwner, isViewer, isGuest } = props;
|
|
||||||
|
|
||||||
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
|
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
|
||||||
|
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
@ -87,8 +83,7 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -98,7 +93,6 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
<BreadcrumbItem title="Estimates Settings" />
|
<BreadcrumbItem title="Estimates Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<CreateUpdateEstimateModal
|
<CreateUpdateEstimateModal
|
||||||
isCreate={estimateToUpdate ? true : false}
|
isCreate={estimateToUpdate ? true : false}
|
||||||
@ -154,25 +148,9 @@ const EstimatesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
</>
|
</>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EstimatesSettings;
|
export default EstimatesSettings;
|
||||||
|
@ -7,10 +7,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track-event.service";
|
||||||
// lib
|
|
||||||
import { requiredAdmin } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -20,12 +18,12 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons";
|
import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons";
|
||||||
import { DocumentTextIcon } from "@heroicons/react/24/outline";
|
import { DocumentTextIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IProject, UserAuth } from "types";
|
import { IProject } from "types";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
const FeaturesSettings: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -79,8 +77,7 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -90,7 +87,6 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
<BreadcrumbItem title="Features Settings" />
|
<BreadcrumbItem title="Features Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<section className="space-y-8">
|
<section className="space-y-8">
|
||||||
<h3 className="text-2xl font-semibold">Features</h3>
|
<h3 className="text-2xl font-semibold">Features</h3>
|
||||||
@ -269,24 +265,8 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FeaturesSettings;
|
export default FeaturesSettings;
|
||||||
|
@ -7,11 +7,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
|
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { IProject, IWorkspace, UserAuth } from "types";
|
|
||||||
// lib
|
|
||||||
import { requiredAdmin } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// components
|
// components
|
||||||
@ -31,7 +28,8 @@ import {
|
|||||||
} from "components/ui";
|
} from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import { IProject, IWorkspace } from "types";
|
||||||
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
@ -44,10 +42,8 @@ const defaultValues: Partial<IProject> = {
|
|||||||
network: 0,
|
network: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGuest }) => {
|
const GeneralSettings: NextPage = () => {
|
||||||
const [selectProject, setSelectedProject] = useState<string | null>(null);
|
const [selectProject, setSelectedProject] = useState<string | null>(null);
|
||||||
const [isImageUploading, setIsImageUploading] = useState(false);
|
|
||||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -136,8 +132,7 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -147,7 +142,6 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
<BreadcrumbItem title="General Settings" />
|
<BreadcrumbItem title="General Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<DeleteProjectModal
|
<DeleteProjectModal
|
||||||
data={projectDetails ?? null}
|
data={projectDetails ?? null}
|
||||||
@ -366,24 +360,8 @@ const GeneralSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GeneralSettings;
|
export default GeneralSettings;
|
||||||
|
@ -4,10 +4,8 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredAdmin } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// services
|
// services
|
||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
@ -19,14 +17,12 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon, PuzzlePieceIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon, PuzzlePieceIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IProject, UserAuth } from "types";
|
import { IProject } from "types";
|
||||||
import type { NextPageContext, NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const ProjectIntegrations: NextPage<UserAuth> = (props) => {
|
const ProjectIntegrations: NextPage = () => {
|
||||||
const { isMember, isOwner, isViewer, isGuest } = props;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -46,8 +42,7 @@ const ProjectIntegrations: NextPage<UserAuth> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -57,7 +52,6 @@ const ProjectIntegrations: NextPage<UserAuth> = (props) => {
|
|||||||
<BreadcrumbItem title="Integrations" />
|
<BreadcrumbItem title="Integrations" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
{workspaceIntegrations ? (
|
{workspaceIntegrations ? (
|
||||||
workspaceIntegrations.length > 0 ? (
|
workspaceIntegrations.length > 0 ? (
|
||||||
@ -97,24 +91,8 @@ const ProjectIntegrations: NextPage<UserAuth> = (props) => {
|
|||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: NextPageContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectIntegrations;
|
export default ProjectIntegrations;
|
||||||
|
@ -10,7 +10,7 @@ import issuesService from "services/issues.service";
|
|||||||
// lib
|
// lib
|
||||||
import { requiredAdmin } from "lib/auth";
|
import { requiredAdmin } from "lib/auth";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
CreateUpdateLabelInline,
|
CreateUpdateLabelInline,
|
||||||
@ -29,9 +29,7 @@ import type { GetServerSidePropsContext, NextPage } from "next";
|
|||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const LabelsSettings: NextPage<UserAuth> = (props) => {
|
const LabelsSettings: NextPage = () => {
|
||||||
const { isMember, isOwner, isViewer, isGuest } = props;
|
|
||||||
|
|
||||||
// create/edit label form
|
// create/edit label form
|
||||||
const [labelForm, setLabelForm] = useState(false);
|
const [labelForm, setLabelForm] = useState(false);
|
||||||
|
|
||||||
@ -99,8 +97,7 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
|
|||||||
handleClose={() => setLabelsListModal(false)}
|
handleClose={() => setLabelsListModal(false)}
|
||||||
parent={parentLabel}
|
parent={parentLabel}
|
||||||
/>
|
/>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -110,7 +107,6 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
|
|||||||
<BreadcrumbItem title="Labels Settings" />
|
<BreadcrumbItem title="Labels Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<section className="grid grid-cols-12 gap-10">
|
<section className="grid grid-cols-12 gap-10">
|
||||||
<div className="col-span-12 sm:col-span-5">
|
<div className="col-span-12 sm:col-span-5">
|
||||||
@ -182,25 +178,9 @@ const LabelsSettings: NextPage<UserAuth> = (props) => {
|
|||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LabelsSettings;
|
export default LabelsSettings;
|
||||||
|
@ -8,12 +8,10 @@ import useSWR from "swr";
|
|||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
// lib
|
|
||||||
import { requiredAdmin } from "lib/auth";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
|
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
|
||||||
import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
|
import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
|
||||||
@ -23,8 +21,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
import { UserAuth } from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
PROJECT_DETAILS,
|
PROJECT_DETAILS,
|
||||||
@ -35,7 +32,7 @@ import {
|
|||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
|
||||||
const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGuest }) => {
|
const MembersSettings: NextPage = () => {
|
||||||
const [inviteModal, setInviteModal] = useState(false);
|
const [inviteModal, setInviteModal] = useState(false);
|
||||||
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
|
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
|
||||||
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null);
|
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null);
|
||||||
@ -148,8 +145,7 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
setIsOpen={setInviteModal}
|
setIsOpen={setInviteModal}
|
||||||
members={members}
|
members={members}
|
||||||
/>
|
/>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -159,7 +155,6 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
<BreadcrumbItem title="Members Settings" />
|
<BreadcrumbItem title="Members Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<section className="space-y-8">
|
<section className="space-y-8">
|
||||||
<div className="flex items-end justify-between gap-4">
|
<div className="flex items-end justify-between gap-4">
|
||||||
@ -274,25 +269,9 @@ const MembersSettings: NextPage<UserAuth> = ({ isMember, isOwner, isViewer, isGu
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MembersSettings;
|
export default MembersSettings;
|
||||||
|
@ -4,13 +4,11 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredAdmin } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/state.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
CreateUpdateStateInline,
|
CreateUpdateStateInline,
|
||||||
@ -26,14 +24,11 @@ import { PlusIcon } from "@heroicons/react/24/outline";
|
|||||||
// helpers
|
// helpers
|
||||||
import { getStatesList, orderStateGroups } from "helpers/state.helper";
|
import { getStatesList, orderStateGroups } from "helpers/state.helper";
|
||||||
// types
|
// types
|
||||||
import { UserAuth } from "types";
|
import type { NextPage } from "next";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS, STATE_LIST } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, STATE_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
const StatesSettings: NextPage<UserAuth> = (props) => {
|
const StatesSettings: NextPage = () => {
|
||||||
const { isMember, isOwner, isViewer, isGuest } = props;
|
|
||||||
|
|
||||||
const [activeGroup, setActiveGroup] = useState<StateGroup>(null);
|
const [activeGroup, setActiveGroup] = useState<StateGroup>(null);
|
||||||
const [selectedState, setSelectedState] = useState<string | null>(null);
|
const [selectedState, setSelectedState] = useState<string | null>(null);
|
||||||
const [selectDeleteState, setSelectDeleteState] = useState<string | null>(null);
|
const [selectDeleteState, setSelectDeleteState] = useState<string | null>(null);
|
||||||
@ -64,8 +59,7 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
data={statesList?.find((s) => s.id === selectDeleteState) ?? null}
|
data={statesList?.find((s) => s.id === selectDeleteState) ?? null}
|
||||||
onClose={() => setSelectDeleteState(null)}
|
onClose={() => setSelectDeleteState(null)}
|
||||||
/>
|
/>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -75,7 +69,6 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
<BreadcrumbItem title="States Settings" />
|
<BreadcrumbItem title="States Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-12 gap-10">
|
<div className="grid grid-cols-12 gap-10">
|
||||||
<div className="col-span-12 sm:col-span-5">
|
<div className="col-span-12 sm:col-span-5">
|
||||||
@ -149,25 +142,9 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StatesSettings;
|
export default StatesSettings;
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { GetServerSidePropsContext } from "next";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import viewsService from "services/views.service";
|
import viewsService from "services/views.service";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// contexts
|
// contexts
|
||||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||||
|
// components
|
||||||
|
import { IssuesFilterView, IssuesView } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, PrimaryButton } from "components/ui";
|
import { CustomMenu, PrimaryButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// icons
|
||||||
import { UserAuth } from "types";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { StackedLayersIcon } from "components/icons";
|
||||||
|
// helpers
|
||||||
|
import { truncateText } from "helpers/string.helper";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS, VIEWS_LIST, VIEW_DETAILS } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, VIEWS_LIST, VIEW_DETAILS } from "constants/fetch-keys";
|
||||||
import { IssuesFilterView, IssuesView } from "components/core";
|
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { truncateText } from "helpers/string.helper";
|
|
||||||
import { StackedLayersIcon } from "components/icons";
|
|
||||||
|
|
||||||
const SingleView: React.FC<UserAuth> = (props) => {
|
const SingleView: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, viewId } = router.query;
|
const { workspaceSlug, projectId, viewId } = router.query;
|
||||||
|
|
||||||
@ -56,8 +54,7 @@ const SingleView: React.FC<UserAuth> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueViewContextProvider>
|
<IssueViewContextProvider>
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -104,39 +101,10 @@ const SingleView: React.FC<UserAuth> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IssuesView userAuth={props} />
|
<IssuesView />
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
</IssueViewContextProvider>
|
</IssueViewContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SingleView;
|
export default SingleView;
|
||||||
|
@ -4,40 +4,32 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import viewsService from "services/views.service";
|
import viewsService from "services/views.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// ui
|
// ui
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
|
|
||||||
//icons
|
//icons
|
||||||
import { PlusIcon } from "components/icons";
|
import { PlusIcon } from "components/icons";
|
||||||
|
// images
|
||||||
// image
|
|
||||||
import emptyView from "public/empty-state/empty-view.svg";
|
import emptyView from "public/empty-state/empty-view.svg";
|
||||||
// fetching keys
|
// fetching keys
|
||||||
import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys";
|
||||||
// components
|
// components
|
||||||
import { PrimaryButton, Loader, EmptyState } from "components/ui";
|
import { PrimaryButton, Loader, EmptyState } from "components/ui";
|
||||||
import { DeleteViewModal, CreateUpdateViewModal, SingleViewItem } from "components/views";
|
import { DeleteViewModal, CreateUpdateViewModal, SingleViewItem } from "components/views";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { IView, UserAuth } from "types";
|
import { IView } from "types";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage } from "next";
|
||||||
|
|
||||||
const ProjectViews: NextPage<UserAuth> = (props) => {
|
const ProjectViews: NextPage = () => {
|
||||||
const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false);
|
const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false);
|
||||||
const [selectedView, setSelectedView] = useState<IView | null>(null);
|
const [selectedView, setSelectedView] = useState<IView | null>(null);
|
||||||
|
|
||||||
const {
|
const router = useRouter();
|
||||||
query: { workspaceSlug, projectId },
|
const { workspaceSlug, projectId } = router.query;
|
||||||
} = useRouter();
|
|
||||||
|
|
||||||
const { data: activeProject } = useSWR(
|
const { data: activeProject } = useSWR(
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||||
@ -54,11 +46,10 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<ProjectAuthorizationWrapper
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - Views",
|
title: "Plane - Views",
|
||||||
}}
|
}}
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||||
@ -116,37 +107,8 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
|
|||||||
<Loader.Item height="30px" />
|
<Loader.Item height="30px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</ProjectAuthorizationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectId = ctx.query.projectId as string;
|
|
||||||
const workspaceSlug = ctx.query.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectViews;
|
export default ProjectViews;
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
// lib
|
|
||||||
import { requiredAuth } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
import useWorkspaces from "hooks/use-workspaces";
|
import useWorkspaces from "hooks/use-workspaces";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import { JoinProjectModal } from "components/project/join-project-modal";
|
import { JoinProjectModal } from "components/project/join-project-modal";
|
||||||
import { DeleteProjectModal, SingleProjectCard } from "components/project";
|
import { DeleteProjectModal, SingleProjectCard } from "components/project";
|
||||||
@ -19,7 +20,7 @@ import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
// image
|
// image
|
||||||
@ -37,7 +38,7 @@ const ProjectsPage: NextPage = () => {
|
|||||||
const [selectedProjectToJoin, setSelectedProjectToJoin] = useState<string | null>(null);
|
const [selectedProjectToJoin, setSelectedProjectToJoin] = useState<string | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<WorkspaceAuthorizationLayout
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Projects`} />
|
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Projects`} />
|
||||||
@ -113,29 +114,8 @@ const ProjectsPage: NextPage = () => {
|
|||||||
<Loader.Item height="100px" />
|
<Loader.Item height="100px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const user = await requiredAuth(ctx.req?.headers.cookie);
|
|
||||||
|
|
||||||
const redirectAfterSignIn = ctx.resolvedUrl;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: `/signin?next=${redirectAfterSignIn}`,
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
user,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectsPage;
|
export default ProjectsPage;
|
||||||
|
@ -4,28 +4,19 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// ui
|
// ui
|
||||||
import { SecondaryButton } from "components/ui";
|
import { SecondaryButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSideProps } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
type TBillingSettingsProps = {
|
const BillingSettings: NextPage = () => {
|
||||||
isOwner: boolean;
|
|
||||||
isMember: boolean;
|
|
||||||
isViewer: boolean;
|
|
||||||
isGuest: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
|
|
||||||
const {
|
const {
|
||||||
query: { workspaceSlug },
|
query: { workspaceSlug },
|
||||||
} = useRouter();
|
} = useRouter();
|
||||||
@ -36,72 +27,44 @@ const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<WorkspaceAuthorizationLayout
|
||||||
<AppLayout
|
breadcrumbs={
|
||||||
memberType={props}
|
<Breadcrumbs>
|
||||||
breadcrumbs={
|
<BreadcrumbItem
|
||||||
<Breadcrumbs>
|
title={`${activeWorkspace?.name ?? "Workspace"}`}
|
||||||
<BreadcrumbItem
|
link={`/${workspaceSlug}`}
|
||||||
title={`${activeWorkspace?.name ?? "Workspace"}`}
|
/>
|
||||||
link={`/${workspaceSlug}`}
|
<BreadcrumbItem title="Members Settings" />
|
||||||
/>
|
</Breadcrumbs>
|
||||||
<BreadcrumbItem title="Members Settings" />
|
}
|
||||||
</Breadcrumbs>
|
>
|
||||||
}
|
<section className="space-y-8">
|
||||||
settingsLayout
|
<div>
|
||||||
>
|
<h3 className="text-3xl font-bold leading-6 text-gray-900">Billing & Plans</h3>
|
||||||
<section className="space-y-8">
|
<p className="mt-4 text-sm text-gray-500">[Free launch preview] plan Pro</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-8 md:w-2/3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-3xl font-bold leading-6 text-gray-900">Billing & Plans</h3>
|
<div className="w-80 rounded-md border bg-white p-4 text-center">
|
||||||
<p className="mt-4 text-sm text-gray-500">[Free launch preview] plan Pro</p>
|
<h4 className="text-md mb-1 leading-6 text-gray-900">Payment due</h4>
|
||||||
</div>
|
<h2 className="text-3xl font-extrabold">--</h2>
|
||||||
<div className="space-y-8 md:w-2/3">
|
|
||||||
<div>
|
|
||||||
<div className="w-80 rounded-md border bg-white p-4 text-center">
|
|
||||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Payment due</h4>
|
|
||||||
<h2 className="text-3xl font-extrabold">--</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Current plan</h4>
|
|
||||||
<p className="mb-3 text-sm text-gray-500">You are currently using the free plan</p>
|
|
||||||
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
|
|
||||||
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Billing history</h4>
|
|
||||||
<p className="mb-3 text-sm text-gray-500">There are no invoices to display</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div>
|
||||||
</AppLayout>
|
<h4 className="text-md mb-1 leading-6 text-gray-900">Current plan</h4>
|
||||||
</>
|
<p className="mb-3 text-sm text-gray-500">You are currently using the free plan</p>
|
||||||
|
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
|
||||||
|
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-md mb-1 leading-6 text-gray-900">Billing history</h4>
|
||||||
|
<p className="mb-3 text-sm text-gray-500">There are no invoices to display</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const workspaceSlug = ctx.params?.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
|
|
||||||
|
|
||||||
if (memberDetail === null) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BillingSettings;
|
export default BillingSettings;
|
||||||
|
@ -1,58 +1,30 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
|
// components
|
||||||
import IntegrationGuide from "components/integration/guide";
|
import IntegrationGuide from "components/integration/guide";
|
||||||
// ui
|
// ui
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// types
|
||||||
import { UserAuth } from "types";
|
import type { NextPage } from "next";
|
||||||
import type { GetServerSideProps, NextPage } from "next";
|
|
||||||
|
|
||||||
const ImportExport: NextPage<UserAuth> = (props) => {
|
const ImportExport: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<WorkspaceAuthorizationLayout
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title={`${workspaceSlug ?? "Workspace"}`} link={`/${workspaceSlug}`} />
|
<BreadcrumbItem title={`${workspaceSlug ?? "Workspace"}`} link={`/${workspaceSlug}`} />
|
||||||
<BreadcrumbItem title="Members Settings" />
|
<BreadcrumbItem title="Members Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<IntegrationGuide />
|
<IntegrationGuide />
|
||||||
</AppLayout>
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const workspaceSlug = ctx.params?.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
|
|
||||||
|
|
||||||
if (memberDetail === null) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ImportExport;
|
export default ImportExport;
|
||||||
|
@ -7,29 +7,26 @@ import useSWR, { mutate } from "swr";
|
|||||||
|
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
|
||||||
// icons
|
|
||||||
import { LinkIcon } from "@heroicons/react/24/outline";
|
|
||||||
// lib
|
|
||||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
|
||||||
// services
|
// services
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
import fileService from "services/file.service";
|
import fileService from "services/file.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import { ImageUploadModal } from "components/core";
|
import { ImageUploadModal } from "components/core";
|
||||||
import { DeleteWorkspaceModal } from "components/workspace";
|
import { DeleteWorkspaceModal } from "components/workspace";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, Input, CustomSelect, OutlineButton, SecondaryButton } from "components/ui";
|
import { Spinner, Input, CustomSelect, OutlineButton, SecondaryButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
|
// icons
|
||||||
|
import { LinkIcon } from "@heroicons/react/24/outline";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import type { IWorkspace, UserAuth } from "types";
|
import type { IWorkspace } from "types";
|
||||||
import type { GetServerSideProps, NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_DETAILS, USER_WORKSPACES } from "constants/fetch-keys";
|
import { WORKSPACE_DETAILS, USER_WORKSPACES } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
@ -42,7 +39,7 @@ const defaultValues: Partial<IWorkspace> = {
|
|||||||
logo: null,
|
logo: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const WorkspaceSettings: NextPage<UserAuth> = (props) => {
|
const WorkspaceSettings: NextPage = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isImageUploading, setIsImageUploading] = useState(false);
|
const [isImageUploading, setIsImageUploading] = useState(false);
|
||||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
||||||
@ -107,8 +104,7 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<WorkspaceAuthorizationLayout
|
||||||
memberType={props}
|
|
||||||
meta={{
|
meta={{
|
||||||
title: "Plane - Workspace Settings",
|
title: "Plane - Workspace Settings",
|
||||||
}}
|
}}
|
||||||
@ -117,7 +113,6 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
|
|||||||
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Settings`} />
|
<BreadcrumbItem title={`${activeWorkspace?.name ?? "Workspace"} Settings`} />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<ImageUploadModal
|
<ImageUploadModal
|
||||||
isOpen={isImageUploadModalOpen}
|
isOpen={isImageUploadModalOpen}
|
||||||
@ -282,32 +277,8 @@ const WorkspaceSettings: NextPage<UserAuth> = (props) => {
|
|||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</WorkspaceAuthorizationLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const workspaceSlug = ctx.params?.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
|
|
||||||
|
|
||||||
if (memberDetail === null) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkspaceSettings;
|
export default WorkspaceSettings;
|
||||||
|
@ -7,22 +7,19 @@ import useSWR from "swr";
|
|||||||
// services
|
// services
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration";
|
||||||
// lib
|
|
||||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// componentss
|
// components
|
||||||
import OAuthPopUp from "components/popup";
|
import OAuthPopUp from "components/popup";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// types
|
// types
|
||||||
import type { NextPage, GetServerSideProps } from "next";
|
import type { NextPage } from "next";
|
||||||
import { UserAuth } from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys";
|
import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const WorkspaceIntegrations: NextPage<UserAuth> = (props) => {
|
const WorkspaceIntegrations: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
@ -36,66 +33,38 @@ const WorkspaceIntegrations: NextPage<UserAuth> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<WorkspaceAuthorizationLayout
|
||||||
<AppLayout
|
breadcrumbs={
|
||||||
memberType={props}
|
<Breadcrumbs>
|
||||||
breadcrumbs={
|
<BreadcrumbItem
|
||||||
<Breadcrumbs>
|
title={`${activeWorkspace?.name ?? "Workspace"}`}
|
||||||
<BreadcrumbItem
|
link={`/${workspaceSlug}`}
|
||||||
title={`${activeWorkspace?.name ?? "Workspace"}`}
|
/>
|
||||||
link={`/${workspaceSlug}`}
|
<BreadcrumbItem title="Integrations" />
|
||||||
/>
|
</Breadcrumbs>
|
||||||
<BreadcrumbItem title="Integrations" />
|
}
|
||||||
</Breadcrumbs>
|
>
|
||||||
}
|
<section className="space-y-8">
|
||||||
settingsLayout
|
<h3 className="text-2xl font-semibold">Integrations</h3>
|
||||||
>
|
<div className="space-y-5">
|
||||||
<section className="space-y-8">
|
{appIntegrations ? (
|
||||||
<h3 className="text-2xl font-semibold">Integrations</h3>
|
appIntegrations.map((integration) => (
|
||||||
<div className="space-y-5">
|
<OAuthPopUp
|
||||||
{appIntegrations ? (
|
key={integration.id}
|
||||||
appIntegrations.map((integration) => (
|
workspaceSlug={workspaceSlug}
|
||||||
<OAuthPopUp
|
integration={integration}
|
||||||
key={integration.id}
|
/>
|
||||||
workspaceSlug={workspaceSlug}
|
))
|
||||||
integration={integration}
|
) : (
|
||||||
/>
|
<Loader className="space-y-5">
|
||||||
))
|
<Loader.Item height="60px" />
|
||||||
) : (
|
<Loader.Item height="60px" />
|
||||||
<Loader className="space-y-5">
|
</Loader>
|
||||||
<Loader.Item height="60px" />
|
)}
|
||||||
<Loader.Item height="60px" />
|
</div>
|
||||||
</Loader>
|
</section>
|
||||||
)}
|
</WorkspaceAuthorizationLayout>
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</AppLayout>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const workspaceSlug = ctx.params?.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
|
|
||||||
|
|
||||||
if (memberDetail === null) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkspaceIntegrations;
|
export default WorkspaceIntegrations;
|
||||||
|
@ -5,14 +5,12 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// lib
|
|
||||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
|
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
|
||||||
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
|
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
|
||||||
@ -22,14 +20,13 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
|||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { GetServerSideProps, NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
import { UserAuth } from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
|
||||||
const MembersSettings: NextPage<UserAuth> = (props) => {
|
const MembersSettings: NextPage = () => {
|
||||||
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
|
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
|
||||||
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null);
|
const [selectedInviteRemoveMember, setSelectedInviteRemoveMember] = useState<string | null>(null);
|
||||||
const [inviteModal, setInviteModal] = useState(false);
|
const [inviteModal, setInviteModal] = useState(false);
|
||||||
@ -129,8 +126,7 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
|
|||||||
workspace_slug={workspaceSlug as string}
|
workspace_slug={workspaceSlug as string}
|
||||||
members={members}
|
members={members}
|
||||||
/>
|
/>
|
||||||
<AppLayout
|
<WorkspaceAuthorizationLayout
|
||||||
memberType={props}
|
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -140,7 +136,6 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
|
|||||||
<BreadcrumbItem title="Members Settings" />
|
<BreadcrumbItem title="Members Settings" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
settingsLayout
|
|
||||||
>
|
>
|
||||||
<section className="space-y-8">
|
<section className="space-y-8">
|
||||||
<div className="flex items-end justify-between gap-4">
|
<div className="flex items-end justify-between gap-4">
|
||||||
@ -254,33 +249,9 @@ const MembersSettings: NextPage<UserAuth> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</AppLayout>
|
</WorkspaceAuthorizationLayout>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const workspaceSlug = ctx.params?.workspaceSlug as string;
|
|
||||||
|
|
||||||
const memberDetail = await requiredWorkspaceAdmin(workspaceSlug, ctx.req.headers.cookie);
|
|
||||||
|
|
||||||
if (memberDetail === null) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
isOwner: memberDetail?.role === 20,
|
|
||||||
isMember: memberDetail?.role === 15,
|
|
||||||
isViewer: memberDetail?.role === 10,
|
|
||||||
isGuest: memberDetail?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MembersSettings;
|
export default MembersSettings;
|
||||||
|
@ -42,7 +42,6 @@ const AppPostInstallation = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getServerSideProps(context: any) {
|
export async function getServerSideProps(context: any) {
|
||||||
console.log(context.query);
|
|
||||||
return {
|
return {
|
||||||
props: context.query,
|
props: context.query,
|
||||||
};
|
};
|
||||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
44
apps/app/public/auth/workspace-not-authorized.svg
Normal file
44
apps/app/public/auth/workspace-not-authorized.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 36 KiB |
@ -136,7 +136,7 @@ class ProjectServices extends APIService {
|
|||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`)
|
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ class WorkspaceService extends APIService {
|
|||||||
return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/me/`)
|
return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/me/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user