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