release: Stage Release (#251)
* feat: manual ordering for issues in kanban * refactor: issues folder structure * refactor: modules and states folder structure * refactor: datepicker code * fix: create issue modal bug * feat: custom progress bar added * refactor: created global component for kanban board * refactor: update cycle and module issue create * refactor: return modules created * refactor: integrated global kanban view everywhere * refactor: integrated global list view everywhere * refactor: removed unnecessary api calls * refactor: update nomenclature for consistency * refactor: global select component for issue view * refactor: track cycles and modules for issue * fix: tracking new cycles and modules in activities * feat: segregate api token workspace * fix: workpsace id during token creation * refactor: update model association to cascade on delete * feat: sentry integrated (#235) * feat: sentry integrated * fix: removed unnecessary env variable * fix: update remirror description to save empty string and empty paragraph (#237) * Update README.md * fix: description and comment_json default value to remove warnings * feat: link option in remirror (#240) * feat: link option in remirror * fix: removed link import from remirror toolbar * feat: module and cycle settings under project * fix: module issue assignment * fix: module issue updation and activity logging * fix: typo while creating module issues * fix: string comparison for update operation * fix: ui fixes (#246) * style: shortcut command label bg color change * sidebar shortcut ui fix --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com> * fix: update empty passwords to hashed string and add hashing for magic sign in * refactor: remove print logs from back migrations * build(deps): bump django in /apiserver/requirements Bumps [django](https://github.com/django/django) from 3.2.16 to 3.2.17. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.2.16...3.2.17) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * feat: cycles and modules toggle in settings, refactor: folder structure (#247) * feat: link option in remirror * fix: removed link import from remirror toolbar * refactor: constants folder * refactor: layouts folder structure * fix: issue view context * feat: cycles and modules toggle in settings --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: sphynxux <122926002+sphynxux@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
6966666bf5
commit
d3b73dc32f
177 changed files with 4767 additions and 5404 deletions
|
|
@ -4,12 +4,13 @@ import Link from "next/link";
|
|||
import Image from "next/image";
|
||||
|
||||
// layouts
|
||||
import type { NextPage } from "next";
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// ui
|
||||
import { Button } from "components/ui";
|
||||
// images
|
||||
import Image404 from "public/404.svg";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
|
||||
const PageNotFound: NextPage = () => (
|
||||
<DefaultLayout
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import React from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// lib
|
||||
import { requiredAuth } from "lib/auth";
|
||||
// layouts
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// hooks
|
||||
import useProjects from "hooks/use-projects";
|
||||
import useWorkspaceDetails from "hooks/use-workspace-details";
|
||||
import useIssues from "hooks/use-issues";
|
||||
// components
|
||||
import { WorkspaceHomeCardsList, WorkspaceHomeGreetings } from "components/workspace";
|
||||
// ui
|
||||
import { Spinner } from "components/ui";
|
||||
// icons
|
||||
import {
|
||||
ArrowRightIcon,
|
||||
CalendarDaysIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
// lib
|
||||
import { requiredAuth } from "lib/auth";
|
||||
// layouts
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// components
|
||||
import { Spinner } from "components/ui";
|
||||
import { WorkspaceHomeCardsList, WorkspaceHomeGreetings } from "components/workspace";
|
||||
// hooks
|
||||
import useProjects from "hooks/use-projects";
|
||||
import useWorkspaceDetails from "hooks/use-workspace-details";
|
||||
import useIssues from "hooks/use-issues";
|
||||
// icons
|
||||
import { LayerDiagonalIcon } from "components/icons";
|
||||
import { getPriorityIcon } from "components/icons/priority-icon";
|
||||
// helpers
|
||||
import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
|
||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||
import { groupBy } from "helpers/array.helper";
|
||||
// types
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
// constants
|
||||
import { getPriorityIcon } from "constants/global";
|
||||
|
||||
const WorkspacePage: NextPage = () => {
|
||||
// router
|
||||
|
|
@ -164,7 +164,7 @@ const WorkspacePage: NextPage = () => {
|
|||
<LayerDiagonalIcon height="56" width="56" />
|
||||
<h3 className="text-gray-500">
|
||||
No issues found. Create a new issue with{" "}
|
||||
<pre className="inline rounded bg-gray-100 px-2 py-1">C</pre>.
|
||||
<pre className="inline rounded bg-gray-200 px-2 py-1">C</pre>.
|
||||
</h3>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -191,7 +191,7 @@ const WorkspacePage: NextPage = () => {
|
|||
<a className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
{project.icon ? (
|
||||
<span className="grid flex-shrink-0 place-items-center rounded uppercase text-white">
|
||||
<span className="grid flex-shrink-0 place-items-center rounded uppercase">
|
||||
{String.fromCodePoint(parseInt(project.icon))}
|
||||
</span>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// headless ui
|
||||
|
|
@ -17,174 +17,194 @@ import useIssuesProperties from "hooks/use-issue-properties";
|
|||
// types
|
||||
import { IIssue, Properties } from "types";
|
||||
// components
|
||||
import { IssueListItem } from "components/issues";
|
||||
import { DeleteIssueModal, MyIssuesListItem } from "components/issues";
|
||||
// helpers
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
|
||||
const MyIssuesPage: NextPage = () => {
|
||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// fetching user issues
|
||||
const { myIssues } = useIssues(workspaceSlug?.toString());
|
||||
const { myIssues } = useIssues(workspaceSlug as string);
|
||||
|
||||
// FIXME: remove this hard-coded value
|
||||
const [properties, setProperties] = useIssuesProperties(
|
||||
workspaceSlug ? (workspaceSlug as string) : undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
const handleDeleteIssue = (issue: IIssue) => {
|
||||
setDeleteIssueModal(true);
|
||||
setIssueToDelete(issue);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem title="My Issues" />
|
||||
</Breadcrumbs>
|
||||
}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
<>
|
||||
<DeleteIssueModal
|
||||
handleClose={() => setDeleteIssueModal(false)}
|
||||
isOpen={deleteIssueModal}
|
||||
data={issueToDelete}
|
||||
/>
|
||||
<AppLayout
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem title="My Issues" />
|
||||
</Breadcrumbs>
|
||||
}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
<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)}
|
||||
>
|
||||
{replaceUnderscoreIfSnakeCase(key)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
<HeaderButton
|
||||
Icon={PlusIcon}
|
||||
label="Add Issue"
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
/>
|
||||
</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-lg bg-white">
|
||||
<div className="rounded-t-lg bg-gray-100 px-4 py-3">
|
||||
<Disclosure.Button>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<span>
|
||||
<ChevronDownIcon
|
||||
className={`h-4 w-4 text-gray-500 ${
|
||||
!open ? "-rotate-90 transform" : ""
|
||||
<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"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
<h2 className="font-medium leading-5">My Issues</h2>
|
||||
<p className="text-sm text-gray-500">{myIssues.length}</p>
|
||||
</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>
|
||||
<div className="divide-y-2">
|
||||
{myIssues.map((issue: IIssue) => (
|
||||
<IssueListItem key={issue.id} issue={issue} properties={properties} />
|
||||
onClick={() => setProperties(key as keyof Properties)}
|
||||
>
|
||||
{replaceUnderscoreIfSnakeCase(key)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</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-100 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>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
<HeaderButton
|
||||
Icon={PlusIcon}
|
||||
label="Add Issue"
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AppLayout>
|
||||
}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col space-y-5">
|
||||
{myIssues ? (
|
||||
<>
|
||||
{myIssues.length > 0 ? (
|
||||
<div className="flex flex-col space-y-5">
|
||||
<Disclosure as="div" defaultOpen>
|
||||
{({ open }) => (
|
||||
<div className="rounded-lg bg-white">
|
||||
<div className="rounded-t-lg bg-gray-100 px-4 py-3">
|
||||
<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>
|
||||
<p className="text-sm text-gray-500">{myIssues.length}</p>
|
||||
</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>
|
||||
<div className="divide-y-2">
|
||||
{myIssues.map((issue: IIssue) => (
|
||||
<MyIssuesListItem
|
||||
key={issue.id}
|
||||
issue={issue}
|
||||
properties={properties}
|
||||
projectId={issue.project}
|
||||
handleDeleteIssue={() => handleDeleteIssue(issue)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</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>
|
||||
</AppLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,29 @@ import React, { useEffect, useState } from "react";
|
|||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// react-hook-form
|
||||
import { useForm } from "react-hook-form";
|
||||
// lib
|
||||
import { requiredAuth } from "lib/auth";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useToast from "hooks/use-toast";
|
||||
// layouts
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// services
|
||||
import userService from "services/user.service";
|
||||
import workspaceService from "services/workspace.service";
|
||||
// components
|
||||
import { ImageUploadModal } from "components/core";
|
||||
// ui
|
||||
import { Button, Input, Spinner } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
import {
|
||||
ChevronRightIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
|
|
@ -14,30 +35,11 @@ import {
|
|||
UserPlusIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
|
||||
// hooks
|
||||
import type { IIssue, IUser } from "types";
|
||||
import useUser from "hooks/use-user";
|
||||
import useToast from "hooks/use-toast";
|
||||
// lib
|
||||
import { requiredAuth } from "lib/auth";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
// layouts
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// constants
|
||||
import { USER_ISSUE, USER_WORKSPACE_INVITATIONS, PROJECTS_LIST } from "constants/fetch-keys";
|
||||
// services
|
||||
import userService from "services/user.service";
|
||||
import workspaceService from "services/workspace.service";
|
||||
// components
|
||||
import { ImageUploadModal } from "components/common/image-upload-modal";
|
||||
// ui
|
||||
import { Button, Input, Spinner } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
// types
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
import type { IIssue, IUser } from "types";
|
||||
// fetch-keys
|
||||
import { USER_ISSUE, USER_WORKSPACE_INVITATIONS, PROJECTS_LIST } from "constants/fetch-keys";
|
||||
|
||||
const defaultValues: Partial<IUser> = {
|
||||
avatar: "",
|
||||
|
|
|
|||
|
|
@ -11,13 +11,9 @@ import AppLayout from "layouts/app-layout";
|
|||
// contexts
|
||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||
// components
|
||||
import CyclesListView from "components/project/cycles/list-view";
|
||||
import CyclesBoardView from "components/project/cycles/board-view";
|
||||
import { CreateUpdateIssueModal } from "components/issues";
|
||||
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
|
||||
import ExistingIssuesListModal from "components/common/existing-issues-list-modal";
|
||||
import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "components/core";
|
||||
import CycleDetailSidebar from "components/project/cycles/cycle-detail-sidebar";
|
||||
import View from "components/core/view";
|
||||
// services
|
||||
import issuesServices from "services/issues.service";
|
||||
import cycleServices from "services/cycles.service";
|
||||
|
|
@ -36,15 +32,14 @@ import {
|
|||
CYCLE_ISSUES,
|
||||
CYCLE_LIST,
|
||||
PROJECT_ISSUES_LIST,
|
||||
PROJECT_MEMBERS,
|
||||
PROJECT_DETAILS,
|
||||
CYCLE_DETAILS,
|
||||
} from "constants/fetch-keys";
|
||||
|
||||
const SingleCycle: React.FC<UserAuth> = (props) => {
|
||||
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
|
||||
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
|
||||
const [cycleSidebar, setCycleSidebar] = useState(true);
|
||||
|
||||
const [preloadedData, setPreloadedData] = useState<
|
||||
|
|
@ -77,6 +72,18 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
: null
|
||||
);
|
||||
|
||||
const { data: cycleDetails } = useSWR(
|
||||
cycleId ? CYCLE_DETAILS(cycleId as string) : null,
|
||||
workspaceSlug && projectId && cycleId
|
||||
? () =>
|
||||
cycleServices.getCycleDetails(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
cycleId as string
|
||||
)
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: cycleIssues } = useSWR<CycleIssueResponse[]>(
|
||||
workspaceSlug && projectId && cycleId ? CYCLE_ISSUES(cycleId as string) : null,
|
||||
workspaceSlug && projectId && cycleId
|
||||
|
|
@ -95,19 +102,6 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
cycle: cycleId as string,
|
||||
}));
|
||||
|
||||
const { data: members } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_MEMBERS(workspaceSlug as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||
: null,
|
||||
{
|
||||
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||
if (err?.status === 403) return;
|
||||
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const openCreateIssueModal = (
|
||||
issue?: IIssue,
|
||||
actionType: "create" | "edit" | "delete" = "create"
|
||||
|
|
@ -138,30 +132,6 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
});
|
||||
};
|
||||
|
||||
const removeIssueFromCycle = (bridgeId: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
mutate<CycleIssueResponse[]>(
|
||||
CYCLE_ISSUES(cycleId as string),
|
||||
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
|
||||
false
|
||||
);
|
||||
|
||||
issuesServices
|
||||
.removeIssueFromCycle(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
cycleId as string,
|
||||
bridgeId
|
||||
)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<IssueViewContextProvider>
|
||||
<CreateUpdateIssueModal
|
||||
|
|
@ -181,11 +151,6 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
issues={issues?.results.filter((i) => !i.issue_cycle) ?? []}
|
||||
handleOnSubmit={handleAddIssuesToCycle}
|
||||
/>
|
||||
<ConfirmIssueDeletion
|
||||
handleClose={() => setDeleteIssue(undefined)}
|
||||
isOpen={!!deleteIssue}
|
||||
data={issues?.results.find((issue) => issue.id === deleteIssue)}
|
||||
/>
|
||||
<AppLayout
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
|
|
@ -200,7 +165,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
label={
|
||||
<>
|
||||
<CyclesIcon className="h-3 w-3" />
|
||||
{cycles?.find((c) => c.id === cycleId)?.name}
|
||||
{cycleDetails?.name}
|
||||
</>
|
||||
}
|
||||
className="ml-1.5"
|
||||
|
|
@ -221,7 +186,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
<div
|
||||
className={`flex items-center gap-2 ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}
|
||||
>
|
||||
<View issues={cycleIssuesArray ?? []} />
|
||||
<IssuesFilterView issues={cycleIssuesArray ?? []} />
|
||||
<button
|
||||
type="button"
|
||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-gray-100 ${
|
||||
|
|
@ -237,22 +202,11 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
{cycleIssuesArray ? (
|
||||
cycleIssuesArray.length > 0 ? (
|
||||
<div className={`h-full ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}>
|
||||
<CyclesListView
|
||||
<IssuesView
|
||||
type="cycle"
|
||||
issues={cycleIssuesArray ?? []}
|
||||
openCreateIssueModal={openCreateIssueModal}
|
||||
openIssuesListModal={openIssuesListModal}
|
||||
removeIssueFromCycle={removeIssueFromCycle}
|
||||
setPreloadedData={setPreloadedData}
|
||||
userAuth={props}
|
||||
/>
|
||||
<CyclesBoardView
|
||||
issues={cycleIssuesArray ?? []}
|
||||
members={members}
|
||||
openCreateIssueModal={openCreateIssueModal}
|
||||
openIssuesListModal={openIssuesListModal}
|
||||
handleDeleteIssue={setDeleteIssue}
|
||||
setPreloadedData={setPreloadedData}
|
||||
userAuth={props}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -270,13 +224,13 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
title="Create a new issue"
|
||||
description="Click to create a new issue inside the cycle."
|
||||
Icon={PlusIcon}
|
||||
action={() => openCreateIssueModal()}
|
||||
action={openCreateIssueModal}
|
||||
/>
|
||||
<EmptySpaceItem
|
||||
title="Add an existing issue"
|
||||
description="Open list"
|
||||
Icon={ListBulletIcon}
|
||||
action={() => openIssuesListModal()}
|
||||
action={openIssuesListModal}
|
||||
/>
|
||||
</EmptySpace>
|
||||
</div>
|
||||
|
|
@ -287,7 +241,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
|||
</div>
|
||||
)}
|
||||
<CycleDetailSidebar
|
||||
cycle={cycles?.find((c) => c.id === (cycleId as string))}
|
||||
cycle={cycleDetails}
|
||||
isOpen={cycleSidebar}
|
||||
cycleIssues={cycleIssues ?? []}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ const ProjectCycles: NextPage = () => {
|
|||
title="Create a new cycle"
|
||||
description={
|
||||
<span>
|
||||
Use <pre className="inline rounded bg-gray-100 px-2 py-1">Q</pre> shortcut to
|
||||
Use <pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre> shortcut to
|
||||
create a new cycle
|
||||
</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,15 @@ import { requiredAdmin, requiredAuth } from "lib/auth";
|
|||
// layouts
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// components
|
||||
import AddAsSubIssue from "components/project/issues/issue-detail/add-as-sub-issue";
|
||||
import IssueDetailSidebar from "components/project/issues/issue-detail/issue-detail-sidebar";
|
||||
import AddIssueComment from "components/project/issues/issue-detail/comment/issue-comment-section";
|
||||
import IssueActivitySection from "components/project/issues/issue-detail/activity";
|
||||
import { IssueDescriptionForm, SubIssueList, CreateUpdateIssueModal } from "components/issues";
|
||||
import {
|
||||
IssueDescriptionForm,
|
||||
SubIssuesList,
|
||||
CreateUpdateIssueModal,
|
||||
IssueDetailsSidebar,
|
||||
IssueActivitySection,
|
||||
AddComment,
|
||||
SubIssuesListModal,
|
||||
} from "components/issues";
|
||||
// ui
|
||||
import { Loader, CustomMenu } from "components/ui";
|
||||
import { Breadcrumbs } from "components/breadcrumbs";
|
||||
|
|
@ -46,13 +50,14 @@ const defaultValues = {
|
|||
blocked_list: [],
|
||||
target_date: new Date().toString(),
|
||||
issue_cycle: null,
|
||||
issue_module: null,
|
||||
labels_list: [],
|
||||
};
|
||||
|
||||
const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
||||
// states
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isAddAsSubIssueOpen, setIsAddAsSubIssueOpen] = useState(false);
|
||||
const [subIssuesListModal, setSubIssuesListModal] = useState(false);
|
||||
const [preloadedData, setPreloadedData] = useState<
|
||||
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
|
||||
>(undefined);
|
||||
|
|
@ -68,7 +73,7 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
: null
|
||||
);
|
||||
|
||||
const { data: subIssues } = useSWR(
|
||||
const { data: subIssues } = useSWR<IIssue[] | undefined>(
|
||||
issueId && workspaceSlug && projectId ? SUB_ISSUES(issueId as string) : null,
|
||||
issueId && workspaceSlug && projectId
|
||||
? () =>
|
||||
|
|
@ -104,25 +109,6 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
defaultValues,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (issueDetails) {
|
||||
mutateIssueActivities();
|
||||
reset({
|
||||
...issueDetails,
|
||||
blockers_list:
|
||||
issueDetails.blockers_list ??
|
||||
issueDetails.blocker_issues?.map((issue) => issue.blocker_issue_detail?.id),
|
||||
blocked_list:
|
||||
issueDetails.blocked_list ??
|
||||
issueDetails.blocked_issues?.map((issue) => issue.blocked_issue_detail?.id),
|
||||
assignees_list:
|
||||
issueDetails.assignees_list ?? issueDetails.assignee_details?.map((user) => user.id),
|
||||
labels_list: issueDetails.labels_list ?? issueDetails.labels,
|
||||
labels: issueDetails.labels_list ?? issueDetails.labels,
|
||||
});
|
||||
}
|
||||
}, [issueDetails, reset, mutateIssueActivities]);
|
||||
|
||||
const submitChanges = useCallback(
|
||||
(formData: Partial<IIssue>) => {
|
||||
if (!workspaceSlug || !projectId || !issueId) return;
|
||||
|
|
@ -140,7 +126,6 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
issuesService
|
||||
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
mutateIssueDetails();
|
||||
mutateIssueActivities();
|
||||
})
|
||||
|
|
@ -154,11 +139,16 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
const handleSubIssueRemove = (issueId: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
mutate<IIssue[]>(
|
||||
SUB_ISSUES(issueDetails?.id ?? ""),
|
||||
(prevData) => prevData?.filter((i) => i.id !== issueId),
|
||||
false
|
||||
);
|
||||
|
||||
issuesService
|
||||
.patchIssue(workspaceSlug as string, projectId as string, issueId, { parent: null })
|
||||
.then((res) => {
|
||||
mutate(SUB_ISSUES(issueDetails?.id ?? ""));
|
||||
mutateIssueActivities();
|
||||
|
||||
mutate<IssueResponse>(
|
||||
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
|
||||
|
|
@ -182,6 +172,25 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!issueDetails) return;
|
||||
|
||||
mutateIssueActivities();
|
||||
reset({
|
||||
...issueDetails,
|
||||
blockers_list:
|
||||
issueDetails.blockers_list ??
|
||||
issueDetails.blocker_issues?.map((issue) => issue.blocker_issue_detail?.id),
|
||||
blocked_list:
|
||||
issueDetails.blocks_list ??
|
||||
issueDetails.blocked_issues?.map((issue) => issue.blocked_issue_detail?.id),
|
||||
assignees_list:
|
||||
issueDetails.assignees_list ?? issueDetails.assignee_details?.map((user) => user.id),
|
||||
labels_list: issueDetails.labels_list ?? issueDetails.labels,
|
||||
labels: issueDetails.labels_list ?? issueDetails.labels,
|
||||
});
|
||||
}, [issueDetails, reset, mutateIssueActivities]);
|
||||
|
||||
const isNotAllowed = props.isGuest || props.isViewer;
|
||||
|
||||
return (
|
||||
|
|
@ -211,10 +220,10 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
{isAddAsSubIssueOpen && (
|
||||
<AddAsSubIssue
|
||||
isOpen={isAddAsSubIssueOpen}
|
||||
setIsOpen={setIsAddAsSubIssueOpen}
|
||||
{subIssuesListModal && (
|
||||
<SubIssuesListModal
|
||||
isOpen={subIssuesListModal}
|
||||
handleClose={() => setSubIssuesListModal(false)}
|
||||
parent={issueDetails}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -275,9 +284,9 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
userAuth={props}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
{issueId && workspaceSlug && projectId && subIssues?.length > 0 ? (
|
||||
<SubIssueList
|
||||
issues={subIssues}
|
||||
{issueId && workspaceSlug && projectId && subIssues && subIssues.length > 0 ? (
|
||||
<SubIssuesList
|
||||
issues={subIssues ?? []}
|
||||
parentIssue={issueDetails}
|
||||
projectId={projectId?.toString()}
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
|
|
@ -309,7 +318,7 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
setIsAddAsSubIssueOpen(true);
|
||||
setSubIssuesListModal(true);
|
||||
setPreloadedData({
|
||||
parent: issueDetails.id,
|
||||
actionType: "createIssue",
|
||||
|
|
@ -329,11 +338,11 @@ const IssueDetailsPage: NextPage<UserAuth> = (props) => {
|
|||
issueActivities={issueActivities || []}
|
||||
mutate={mutateIssueActivities}
|
||||
/>
|
||||
<AddIssueComment mutate={mutateIssueActivities} />
|
||||
<AddComment mutate={mutateIssueActivities} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="basis-1/3 space-y-5 border-l p-5">
|
||||
<IssueDetailSidebar
|
||||
<IssueDetailsSidebar
|
||||
control={control}
|
||||
issueDetail={issueDetails}
|
||||
submitChanges={submitChanges}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { RectangleStackIcon } from "@heroicons/react/24/outline";
|
||||
import { PlusIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// lib
|
||||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
||||
// services
|
||||
|
|
@ -13,30 +12,21 @@ import AppLayout from "layouts/app-layout";
|
|||
// contexts
|
||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||
// components
|
||||
import ListView from "components/project/issues/list-view";
|
||||
import BoardView from "components/project/issues/BoardView";
|
||||
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
|
||||
import { CreateUpdateIssueModal } from "components/issues";
|
||||
import View from "components/core/view";
|
||||
import { IssuesFilterView, IssuesView } from "components/core";
|
||||
// ui
|
||||
import { Spinner, EmptySpace, EmptySpaceItem, HeaderButton } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
import { RectangleStackIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import type { IIssue, UserAuth } from "types";
|
||||
import type { UserAuth } from "types";
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
// fetch-keys
|
||||
import { PROJECT_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
|
||||
const ProjectIssues: NextPage<UserAuth> = (props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedIssue, setSelectedIssue] = useState<
|
||||
(IIssue & { actionType: "edit" | "delete" }) | undefined
|
||||
>(undefined);
|
||||
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
|
||||
|
||||
const {
|
||||
query: { workspaceSlug, projectId },
|
||||
} = useRouter();
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { data: projectIssues } = useSWR(
|
||||
workspaceSlug && projectId
|
||||
|
|
@ -54,20 +44,6 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
|
|||
: null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
const timer = setTimeout(() => {
|
||||
setSelectedIssue(undefined);
|
||||
clearTimeout(timer);
|
||||
}, 500);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleEditIssue = (issue: IIssue) => {
|
||||
setIsOpen(true);
|
||||
setSelectedIssue({ ...issue, actionType: "edit" });
|
||||
};
|
||||
|
||||
return (
|
||||
<IssueViewContextProvider>
|
||||
<AppLayout
|
||||
|
|
@ -79,7 +55,9 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
|
|||
}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
<View issues={projectIssues?.results.filter((p) => p.parent === null) ?? []} />
|
||||
<IssuesFilterView
|
||||
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
|
||||
/>
|
||||
<HeaderButton
|
||||
Icon={PlusIcon}
|
||||
label="Add Issue"
|
||||
|
|
@ -93,54 +71,42 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<CreateUpdateIssueModal
|
||||
isOpen={isOpen && selectedIssue?.actionType !== "delete"}
|
||||
prePopulateData={{ ...selectedIssue }}
|
||||
handleClose={() => setIsOpen(false)}
|
||||
data={selectedIssue}
|
||||
/>
|
||||
<ConfirmIssueDeletion
|
||||
handleClose={() => setDeleteIssue(undefined)}
|
||||
isOpen={!!deleteIssue}
|
||||
data={projectIssues?.results.find((issue) => issue.id === deleteIssue)}
|
||||
/>
|
||||
{!projectIssues ? (
|
||||
{projectIssues ? (
|
||||
projectIssues.count > 0 ? (
|
||||
<IssuesView
|
||||
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
|
||||
userAuth={props}
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
||||
<EmptySpace
|
||||
title="You don't have any issue 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>
|
||||
) : projectIssues.count > 0 ? (
|
||||
<>
|
||||
<ListView
|
||||
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
|
||||
handleEditIssue={handleEditIssue}
|
||||
userAuth={props}
|
||||
/>
|
||||
<BoardView
|
||||
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
|
||||
handleDeleteIssue={setDeleteIssue}
|
||||
userAuth={props}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
||||
<EmptySpace
|
||||
title="You don't have any issue 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-100 px-2 py-1">C</pre> shortcut to
|
||||
create a new issue
|
||||
</span>
|
||||
}
|
||||
Icon={PlusIcon}
|
||||
action={() => setIsOpen(true)}
|
||||
/>
|
||||
</EmptySpace>
|
||||
</div>
|
||||
)}
|
||||
</AppLayout>
|
||||
</IssueViewContextProvider>
|
||||
|
|
|
|||
|
|
@ -8,21 +8,15 @@ import useSWR, { mutate } from "swr";
|
|||
import { requiredAdmin, requiredAuth } from "lib/auth";
|
||||
// services
|
||||
import modulesService from "services/modules.service";
|
||||
import projectService from "services/project.service";
|
||||
import issuesService from "services/issues.service";
|
||||
// layouts
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// contexts
|
||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||
// components
|
||||
import ExistingIssuesListModal from "components/common/existing-issues-list-modal";
|
||||
import ModulesBoardView from "components/project/modules/board-view";
|
||||
import ModulesListView from "components/project/modules/list-view";
|
||||
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
|
||||
import ModuleDetailSidebar from "components/project/modules/module-detail-sidebar";
|
||||
import ConfirmModuleDeletion from "components/project/modules/confirm-module-deletion";
|
||||
import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "components/core";
|
||||
import { CreateUpdateIssueModal } from "components/issues";
|
||||
import View from "components/core/view";
|
||||
import { DeleteModuleModal, ModuleDetailsSidebar } from "components/modules";
|
||||
// ui
|
||||
import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
|
|
@ -46,11 +40,10 @@ import {
|
|||
import { NextPageContext } from "next";
|
||||
// fetch-keys
|
||||
import {
|
||||
MODULE_DETAIL,
|
||||
MODULE_DETAILS,
|
||||
MODULE_ISSUES,
|
||||
MODULE_LIST,
|
||||
PROJECT_ISSUES_LIST,
|
||||
PROJECT_MEMBERS,
|
||||
} from "constants/fetch-keys";
|
||||
|
||||
const SingleModule: React.FC<UserAuth> = (props) => {
|
||||
|
|
@ -59,7 +52,6 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>(null);
|
||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
|
||||
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
|
||||
const [selectedModuleForDelete, setSelectedModuleForDelete] = useState<SelectModuleType>();
|
||||
const [preloadedData, setPreloadedData] = useState<
|
||||
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | null
|
||||
|
|
@ -96,8 +88,8 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
: null
|
||||
);
|
||||
|
||||
const { data: moduleDetail } = useSWR<IModule>(
|
||||
MODULE_DETAIL,
|
||||
const { data: moduleDetails } = useSWR<IModule>(
|
||||
moduleId ? MODULE_DETAILS(moduleId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () =>
|
||||
modulesService.getModuleDetails(
|
||||
|
|
@ -108,19 +100,6 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
: null
|
||||
);
|
||||
|
||||
const { data: members } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||
: null,
|
||||
{
|
||||
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||
if (err?.status === 403) return;
|
||||
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const moduleIssuesArray = moduleIssues?.map((issue) => ({
|
||||
...issue.issue_detail,
|
||||
sub_issues_count: issue.sub_issues_count,
|
||||
|
|
@ -156,34 +135,10 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
setModuleIssuesListModal(true);
|
||||
};
|
||||
|
||||
const removeIssueFromModule = (issueId: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
mutate<ModuleIssueResponse[]>(
|
||||
MODULE_ISSUES(moduleId as string),
|
||||
(prevData) => prevData?.filter((p) => p.id !== issueId),
|
||||
false
|
||||
);
|
||||
|
||||
modulesService
|
||||
.removeIssueFromModule(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
moduleId as string,
|
||||
issueId
|
||||
)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteModule = () => {
|
||||
if (!moduleDetail) return;
|
||||
if (!moduleDetails) return;
|
||||
|
||||
setSelectedModuleForDelete({ ...moduleDetail, actionType: "delete" });
|
||||
setSelectedModuleForDelete({ ...moduleDetails, actionType: "delete" });
|
||||
setModuleDeleteModal(true);
|
||||
};
|
||||
|
||||
|
|
@ -208,12 +163,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
issues={issues?.results.filter((i) => !i.issue_module) ?? []}
|
||||
handleOnSubmit={handleAddIssuesToModule}
|
||||
/>
|
||||
<ConfirmIssueDeletion
|
||||
handleClose={() => setDeleteIssue(undefined)}
|
||||
isOpen={!!deleteIssue}
|
||||
data={moduleIssuesArray?.find((issue) => issue.id === deleteIssue)}
|
||||
/>
|
||||
<ConfirmModuleDeletion
|
||||
<DeleteModuleModal
|
||||
isOpen={
|
||||
moduleDeleteModal &&
|
||||
!!selectedModuleForDelete &&
|
||||
|
|
@ -226,7 +176,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
title={`${moduleDetail?.project_detail.name ?? "Project"} Modules`}
|
||||
title={`${moduleDetails?.project_detail.name ?? "Project"} Modules`}
|
||||
link={`/${workspaceSlug}/projects/${projectId}/modules`}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
|
|
@ -236,7 +186,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
label={
|
||||
<>
|
||||
<RectangleGroupIcon className="h-3 w-3" />
|
||||
{modules?.find((c) => c.id === moduleId)?.name}
|
||||
{moduleDetails?.name}
|
||||
</>
|
||||
}
|
||||
className="ml-1.5"
|
||||
|
|
@ -257,7 +207,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
<div
|
||||
className={`flex items-center gap-2 ${moduleSidebar ? "mr-[24rem]" : ""} duration-300`}
|
||||
>
|
||||
<View issues={moduleIssuesArray ?? []} />
|
||||
<IssuesFilterView issues={moduleIssuesArray ?? []} />
|
||||
<button
|
||||
type="button"
|
||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-gray-100 ${
|
||||
|
|
@ -273,22 +223,11 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
{moduleIssuesArray ? (
|
||||
moduleIssuesArray.length > 0 ? (
|
||||
<div className={`h-full ${moduleSidebar ? "mr-[24rem]" : ""} duration-300`}>
|
||||
<ModulesListView
|
||||
<IssuesView
|
||||
type="module"
|
||||
issues={moduleIssuesArray ?? []}
|
||||
openCreateIssueModal={openCreateIssueModal}
|
||||
openIssuesListModal={openIssuesListModal}
|
||||
removeIssueFromModule={removeIssueFromModule}
|
||||
setPreloadedData={setPreloadedData}
|
||||
userAuth={props}
|
||||
/>
|
||||
<ModulesBoardView
|
||||
issues={moduleIssuesArray ?? []}
|
||||
members={members}
|
||||
openCreateIssueModal={openCreateIssueModal}
|
||||
openIssuesListModal={openIssuesListModal}
|
||||
handleDeleteIssue={setDeleteIssue}
|
||||
setPreloadedData={setPreloadedData}
|
||||
userAuth={props}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -306,13 +245,13 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
title="Create a new issue"
|
||||
description="Click to create a new issue inside the module."
|
||||
Icon={PlusIcon}
|
||||
action={() => openCreateIssueModal()}
|
||||
action={openCreateIssueModal}
|
||||
/>
|
||||
<EmptySpaceItem
|
||||
title="Add an existing issue"
|
||||
description="Open list"
|
||||
Icon={ListBulletIcon}
|
||||
action={() => openIssuesListModal()}
|
||||
action={openIssuesListModal}
|
||||
/>
|
||||
</EmptySpace>
|
||||
</div>
|
||||
|
|
@ -322,8 +261,8 @@ const SingleModule: React.FC<UserAuth> = (props) => {
|
|||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
<ModuleDetailSidebar
|
||||
module={modules?.find((m) => m.id === moduleId)}
|
||||
<ModuleDetailsSidebar
|
||||
module={moduleDetails}
|
||||
isOpen={moduleSidebar}
|
||||
moduleIssues={moduleIssues}
|
||||
handleDeleteModule={handleDeleteModule}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { requiredAuth } from "lib/auth";
|
|||
import projectService from "services/project.service";
|
||||
import modulesService from "services/modules.service";
|
||||
// components
|
||||
import SingleModuleCard from "components/project/modules/single-module-card";
|
||||
import { SingleModuleCard } from "components/modules";
|
||||
// ui
|
||||
import { EmptySpace, EmptySpaceItem, HeaderButton, Loader } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
|
|
@ -85,7 +85,7 @@ const ProjectModules: NextPage = () => {
|
|||
title="Create a new module"
|
||||
description={
|
||||
<span>
|
||||
Use <pre className="inline rounded bg-gray-100 px-2 py-1">M</pre> shortcut to
|
||||
Use <pre className="inline rounded bg-gray-200 px-2 py-1">M</pre> shortcut to
|
||||
create a new module
|
||||
</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
// lib
|
||||
import { requiredAdmin } from "lib/auth";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
import workspaceService from "services/workspace.service";
|
||||
|
|
@ -103,8 +103,8 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<SettingsLayout
|
||||
type="project"
|
||||
<AppLayout
|
||||
settingsLayout="project"
|
||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
|
|
@ -247,7 +247,7 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
// lib
|
||||
import { requiredAdmin } from "lib/auth";
|
||||
// layouts
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Button } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// types
|
||||
import { IProject, UserAuth } from "types";
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
// fetch-keys
|
||||
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { data: projectDetails } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.getProject(workspaceSlug as string, projectId as string)
|
||||
: null
|
||||
);
|
||||
|
||||
const handleSubmit = async (formData: Partial<IProject>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
mutate<IProject>(
|
||||
PROJECT_DETAILS(projectId as string),
|
||||
(prevData) => ({ ...(prevData as IProject), ...formData }),
|
||||
false
|
||||
);
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string),
|
||||
(prevData) =>
|
||||
prevData?.map((p) => {
|
||||
if (p.id === projectId)
|
||||
return {
|
||||
...p,
|
||||
...formData,
|
||||
};
|
||||
|
||||
return p;
|
||||
}),
|
||||
false
|
||||
);
|
||||
|
||||
await projectService
|
||||
.updateProject(workspaceSlug as string, projectId as string, formData)
|
||||
.then((res) => {
|
||||
mutate(PROJECT_DETAILS(projectId as string));
|
||||
mutate(PROJECTS_LIST(workspaceSlug as string));
|
||||
|
||||
setToastAlert({
|
||||
title: "Success!",
|
||||
type: "success",
|
||||
message: "Project features updated successfully.",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
settingsLayout="project"
|
||||
memberType={props}
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
title={`${projectDetails?.name ?? "Project"}`}
|
||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||
/>
|
||||
<BreadcrumbItem title="Features Settings" />
|
||||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
<section className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-3xl font-bold leading-6 text-gray-900">Project Features</h3>
|
||||
</div>
|
||||
<div className="space-y-8 md:w-2/3">
|
||||
<div className="flex items-center justify-between gap-x-10 gap-y-2">
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Use cycles</h4>
|
||||
<p className="mb-3 text-sm text-gray-500">
|
||||
Cycles are enabled for all the projects in this workspace. Access it from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
projectDetails?.cycle_view ? "bg-indigo-500" : "bg-gray-200"
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={projectDetails?.cycle_view}
|
||||
onClick={() => handleSubmit({ cycle_view: !projectDetails?.cycle_view })}
|
||||
>
|
||||
<span className="sr-only">Use cycles</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||
projectDetails?.cycle_view ? "translate-x-5" : "translate-x-0"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-x-10 gap-y-2">
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Use modules</h4>
|
||||
<p className="mb-3 text-sm text-gray-500">
|
||||
Modules are enabled for all the projects in this workspace. Access it from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
projectDetails?.module_view ? "bg-indigo-500" : "bg-gray-200"
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={projectDetails?.module_view}
|
||||
onClick={() => handleSubmit({ module_view: !projectDetails?.module_view })}
|
||||
>
|
||||
<span className="sr-only">Use cycles</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||
projectDetails?.module_view ? "translate-x-5" : "translate-x-0"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<a href="https://plane.so/" target="_blank" rel="noreferrer">
|
||||
<Button theme="secondary" size="rg" className="text-xs">
|
||||
Plane is open-source, view Roadmap
|
||||
</Button>
|
||||
</a>
|
||||
<a href="https://github.com/makeplane/plane" target="_blank" rel="noreferrer">
|
||||
<Button theme="secondary" size="rg" className="text-xs">
|
||||
Star us on GitHub
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</AppLayout>
|
||||
);
|
||||
};
|
||||
|
||||
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 FeaturesSettings;
|
||||
|
|
@ -6,11 +6,11 @@ import useSWR, { mutate } from "swr";
|
|||
|
||||
// react-hook-form
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { IProject, IWorkspace } from "types";
|
||||
import { IProject, IWorkspace, UserAuth } from "types";
|
||||
// lib
|
||||
import { requiredAdmin } from "lib/auth";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
import workspaceService from "services/workspace.service";
|
||||
|
|
@ -24,13 +24,13 @@ import { Button, Input, TextArea, Loader, CustomSelect } from "components/ui";
|
|||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
import OutlineButton from "components/ui/outline-button";
|
||||
// helpers
|
||||
import { debounce } from "helpers/functions.helper";
|
||||
import { debounce } from "helpers/common.helper";
|
||||
// types
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
// fetch-keys
|
||||
import { PROJECTS_LIST, PROJECT_DETAILS, WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { NETWORK_CHOICES } from "constants/";
|
||||
import { NETWORK_CHOICES } from "constants/project";
|
||||
|
||||
const defaultValues: Partial<IProject> = {
|
||||
name: "",
|
||||
|
|
@ -39,14 +39,7 @@ const defaultValues: Partial<IProject> = {
|
|||
network: 0,
|
||||
};
|
||||
|
||||
type TGeneralSettingsProps = {
|
||||
isMember: boolean;
|
||||
isOwner: boolean;
|
||||
isViewer: boolean;
|
||||
isGuest: boolean;
|
||||
};
|
||||
|
||||
const GeneralSettings: NextPage<TGeneralSettingsProps> = (props) => {
|
||||
const GeneralSettings: NextPage<UserAuth> = (props) => {
|
||||
const { isMember, isOwner, isViewer, isGuest } = props;
|
||||
|
||||
const [selectProject, setSelectedProject] = useState<string | null>(null);
|
||||
|
|
@ -100,6 +93,7 @@ const GeneralSettings: NextPage<TGeneralSettingsProps> = (props) => {
|
|||
|
||||
const onSubmit = async (formData: IProject) => {
|
||||
if (!activeWorkspace || !projectDetails) return;
|
||||
|
||||
const payload: Partial<IProject> = {
|
||||
name: formData.name,
|
||||
network: formData.network,
|
||||
|
|
@ -109,6 +103,7 @@ const GeneralSettings: NextPage<TGeneralSettingsProps> = (props) => {
|
|||
project_lead: formData.project_lead,
|
||||
icon: formData.icon,
|
||||
};
|
||||
|
||||
await projectService
|
||||
.updateProject(activeWorkspace.slug, projectDetails.id, payload)
|
||||
.then((res) => {
|
||||
|
|
@ -130,9 +125,9 @@ const GeneralSettings: NextPage<TGeneralSettingsProps> = (props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<SettingsLayout
|
||||
<AppLayout
|
||||
settingsLayout="project"
|
||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
||||
type="project"
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
|
|
@ -341,7 +336,7 @@ const GeneralSettings: NextPage<TGeneralSettingsProps> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import React, { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// react-hook-form
|
||||
import { Controller, SubmitHandler, useForm } from "react-hook-form";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// react-color
|
||||
import { TwitterPicker } from "react-color";
|
||||
import type { NextPageContext, NextPage } from "next";
|
||||
// headless ui
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
import workspaceService from "services/workspace.service";
|
||||
|
|
@ -13,20 +17,23 @@ import issuesService from "services/issues.service";
|
|||
// lib
|
||||
import { requiredAdmin } from "lib/auth";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// components
|
||||
import SingleLabel from "components/project/settings/single-label";
|
||||
// ui
|
||||
import { Button, Input, Loader } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// fetch-keys
|
||||
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS, WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { IIssueLabels } from "types";
|
||||
import type { NextPageContext, NextPage } from "next";
|
||||
// fetch-keys
|
||||
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS, WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
const defaultValues: Partial<IIssueLabels> = {
|
||||
name: "",
|
||||
colour: "#ff0000",
|
||||
color: "#ff0000",
|
||||
};
|
||||
|
||||
type TLabelSettingsProps = {
|
||||
|
|
@ -52,7 +59,7 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
|
||||
);
|
||||
|
||||
const { data: activeProject } = useSWR(
|
||||
const { data: projectDetails } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.getProject(workspaceSlug as string, projectId as string)
|
||||
|
|
@ -77,9 +84,9 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
);
|
||||
|
||||
const handleNewLabel: SubmitHandler<IIssueLabels> = async (formData) => {
|
||||
if (!activeWorkspace || !activeProject || isSubmitting) return;
|
||||
if (!activeWorkspace || !projectDetails || isSubmitting) return;
|
||||
await issuesService
|
||||
.createIssueLabel(activeWorkspace.slug, activeProject.id, formData)
|
||||
.createIssueLabel(activeWorkspace.slug, projectDetails.id, formData)
|
||||
.then((res) => {
|
||||
reset(defaultValues);
|
||||
mutate((prevData) => [...(prevData ?? []), res], false);
|
||||
|
|
@ -89,16 +96,17 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
|
||||
const editLabel = (label: IIssueLabels) => {
|
||||
setNewLabelForm(true);
|
||||
setValue("colour", label.colour);
|
||||
setValue("color", label.color);
|
||||
setValue("name", label.name);
|
||||
setIsUpdating(true);
|
||||
setLabelIdForUpdate(label.id);
|
||||
};
|
||||
|
||||
const handleLabelUpdate: SubmitHandler<IIssueLabels> = async (formData) => {
|
||||
if (!activeWorkspace || !activeProject || isSubmitting) return;
|
||||
if (!activeWorkspace || !projectDetails || isSubmitting) return;
|
||||
|
||||
await issuesService
|
||||
.patchIssueLabel(activeWorkspace.slug, activeProject.id, labelIdForUpdate ?? "", formData)
|
||||
.patchIssueLabel(activeWorkspace.slug, projectDetails.id, labelIdForUpdate ?? "", formData)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
reset(defaultValues);
|
||||
|
|
@ -112,10 +120,10 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
};
|
||||
|
||||
const handleLabelDelete = (labelId: string) => {
|
||||
if (activeWorkspace && activeProject) {
|
||||
if (activeWorkspace && projectDetails) {
|
||||
mutate((prevData) => prevData?.filter((p) => p.id !== labelId), false);
|
||||
issuesService
|
||||
.deleteIssueLabel(activeWorkspace.slug, activeProject.id, labelId)
|
||||
.deleteIssueLabel(activeWorkspace.slug, projectDetails.id, labelId)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
})
|
||||
|
|
@ -126,14 +134,14 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<SettingsLayout
|
||||
type="project"
|
||||
<AppLayout
|
||||
settingsLayout="project"
|
||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
title={`${activeProject?.name ?? "Project"}`}
|
||||
link={`/${workspaceSlug}/projects/${activeProject?.id}/issues`}
|
||||
title={`${projectDetails?.name ?? "Project"}`}
|
||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||
/>
|
||||
<BreadcrumbItem title="Labels Settings" />
|
||||
</Breadcrumbs>
|
||||
|
|
@ -170,13 +178,13 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
open ? "text-gray-900" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{watch("colour") && watch("colour") !== "" && (
|
||||
{watch("color") && watch("color") !== "" && (
|
||||
<span
|
||||
className="h-4 w-4 rounded"
|
||||
style={{
|
||||
backgroundColor: watch("colour") ?? "green",
|
||||
backgroundColor: watch("color") ?? "green",
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
</Popover.Button>
|
||||
|
||||
|
|
@ -191,7 +199,7 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
>
|
||||
<Popover.Panel className="absolute top-full left-0 z-20 mt-3 w-screen max-w-xs px-2 sm:px-0">
|
||||
<Controller
|
||||
name="colour"
|
||||
name="color"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<TwitterPicker
|
||||
|
|
@ -258,7 +266,7 @@ const LabelsSettings: NextPage<TLabelSettingsProps> = (props) => {
|
|||
</>
|
||||
</div>
|
||||
</section>
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import { useState } from "react";
|
|||
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
|
|
@ -13,10 +12,8 @@ import workspaceService from "services/workspace.service";
|
|||
import { requiredAdmin } from "lib/auth";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// constants
|
||||
import { ROLE } from "constants/";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// components
|
||||
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
|
||||
import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
|
||||
|
|
@ -24,6 +21,9 @@ import SendProjectInvitationModal from "components/project/send-project-invitati
|
|||
import { Button, CustomListbox, CustomMenu, Loader } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
// fetch-keys
|
||||
import {
|
||||
PROJECT_DETAILS,
|
||||
|
|
@ -31,6 +31,8 @@ import {
|
|||
PROJECT_MEMBERS,
|
||||
WORKSPACE_DETAILS,
|
||||
} from "constants/fetch-keys";
|
||||
// constants
|
||||
import { ROLE } from "constants/workspace";
|
||||
|
||||
type TMemberSettingsProps = {
|
||||
isMember: boolean;
|
||||
|
|
@ -58,7 +60,7 @@ const MembersSettings: NextPage<TMemberSettingsProps> = (props) => {
|
|||
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
|
||||
);
|
||||
|
||||
const { data: activeProject } = useSWR(
|
||||
const { data: projectDetails } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.getProject(workspaceSlug as string, projectId as string)
|
||||
|
|
@ -120,11 +122,11 @@ const MembersSettings: NextPage<TMemberSettingsProps> = (props) => {
|
|||
(item) => item.id === selectedRemoveMember || item.id === selectedInviteRemoveMember
|
||||
)}
|
||||
handleDelete={async () => {
|
||||
if (!activeWorkspace || !activeProject) return;
|
||||
if (!activeWorkspace || !projectDetails) return;
|
||||
if (selectedRemoveMember) {
|
||||
await projectService.deleteProjectMember(
|
||||
activeWorkspace.slug,
|
||||
activeProject.id,
|
||||
projectDetails.id,
|
||||
selectedRemoveMember
|
||||
);
|
||||
mutateMembers(
|
||||
|
|
@ -135,7 +137,7 @@ const MembersSettings: NextPage<TMemberSettingsProps> = (props) => {
|
|||
if (selectedInviteRemoveMember) {
|
||||
await projectService.deleteProjectInvitation(
|
||||
activeWorkspace.slug,
|
||||
activeProject.id,
|
||||
projectDetails.id,
|
||||
selectedInviteRemoveMember
|
||||
);
|
||||
mutateInvitations(
|
||||
|
|
@ -155,14 +157,14 @@ const MembersSettings: NextPage<TMemberSettingsProps> = (props) => {
|
|||
setIsOpen={setInviteModal}
|
||||
members={members}
|
||||
/>
|
||||
<SettingsLayout
|
||||
type="project"
|
||||
<AppLayout
|
||||
settingsLayout="project"
|
||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
title={`${activeProject?.name ?? "Project"}`}
|
||||
link={`/${workspaceSlug}/projects/${activeProject?.id}/issues`}
|
||||
title={`${projectDetails?.name ?? "Project"}`}
|
||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||
/>
|
||||
<BreadcrumbItem title="Members Settings" />
|
||||
</Breadcrumbs>
|
||||
|
|
@ -235,11 +237,11 @@ const MembersSettings: NextPage<TMemberSettingsProps> = (props) => {
|
|||
title={ROLE[member.role as keyof typeof ROLE] ?? "Select Role"}
|
||||
value={member.role}
|
||||
onChange={(value) => {
|
||||
if (!activeWorkspace || !activeProject) return;
|
||||
if (!activeWorkspace || !projectDetails) return;
|
||||
projectService
|
||||
.updateProjectMember(
|
||||
activeWorkspace.slug,
|
||||
activeProject.id,
|
||||
projectDetails.id,
|
||||
member.id,
|
||||
{
|
||||
role: value,
|
||||
|
|
@ -306,7 +308,7 @@ const MembersSettings: NextPage<TMemberSettingsProps> = (props) => {
|
|||
</div>
|
||||
)}
|
||||
</section>
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,13 +11,9 @@ import projectService from "services/project.service";
|
|||
// lib
|
||||
import { requiredAdmin } from "lib/auth";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// components
|
||||
import ConfirmStateDeletion from "components/project/issues/BoardView/state/confirm-state-delete";
|
||||
import {
|
||||
CreateUpdateStateInline,
|
||||
StateGroup,
|
||||
} from "components/project/issues/BoardView/state/create-update-state-inline";
|
||||
import { CreateUpdateStateInline, DeleteStateModal, StateGroup } from "components/states";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
|
|
@ -47,7 +43,7 @@ const StatesSettings: NextPage<TStateSettingsProps> = (props) => {
|
|||
query: { workspaceSlug, projectId },
|
||||
} = useRouter();
|
||||
|
||||
const { data: activeProject } = useSWR(
|
||||
const { data: projectDetails } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.getProject(workspaceSlug as string, projectId as string)
|
||||
|
|
@ -67,19 +63,19 @@ const StatesSettings: NextPage<TStateSettingsProps> = (props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ConfirmStateDeletion
|
||||
<DeleteStateModal
|
||||
isOpen={!!selectDeleteState}
|
||||
data={states?.find((state) => state.id === selectDeleteState) ?? null}
|
||||
onClose={() => setSelectDeleteState(null)}
|
||||
/>
|
||||
<SettingsLayout
|
||||
type="project"
|
||||
<AppLayout
|
||||
settingsLayout="project"
|
||||
memberType={{ isMember, isOwner, isViewer, isGuest }}
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
title={`${activeProject?.name ?? "Project"}`}
|
||||
link={`/${workspaceSlug}/projects/${activeProject?.id}/issues`}
|
||||
title={`${projectDetails?.name ?? "Project"}`}
|
||||
link={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}
|
||||
/>
|
||||
<BreadcrumbItem title="States Settings" />
|
||||
</Breadcrumbs>
|
||||
|
|
@ -91,7 +87,7 @@ const StatesSettings: NextPage<TStateSettingsProps> = (props) => {
|
|||
<p className="mt-4 text-sm text-gray-500">Manage the state of this project.</p>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between gap-4">
|
||||
{states && activeProject ? (
|
||||
{states && projectDetails ? (
|
||||
Object.keys(groupedStates).map((key) => (
|
||||
<div key={key}>
|
||||
<div className="mb-2 flex w-full justify-between md:w-2/3">
|
||||
|
|
@ -108,7 +104,7 @@ const StatesSettings: NextPage<TStateSettingsProps> = (props) => {
|
|||
<div className="space-y-1 rounded-xl border p-1 md:w-2/3">
|
||||
{key === activeGroup && (
|
||||
<CreateUpdateStateInline
|
||||
projectId={activeProject.id}
|
||||
projectId={projectDetails.id}
|
||||
onClose={() => {
|
||||
setActiveGroup(null);
|
||||
setSelectedState(null);
|
||||
|
|
@ -147,7 +143,7 @@ const StatesSettings: NextPage<TStateSettingsProps> = (props) => {
|
|||
) : (
|
||||
<div className="border-b last:border-b-0" key={state.id}>
|
||||
<CreateUpdateStateInline
|
||||
projectId={activeProject.id}
|
||||
projectId={projectDetails.id}
|
||||
onClose={() => {
|
||||
setActiveGroup(null);
|
||||
setSelectedState(null);
|
||||
|
|
@ -172,7 +168,7 @@ const StatesSettings: NextPage<TStateSettingsProps> = (props) => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ const ProjectsPage: NextPage = () => {
|
|||
title="Create a new project"
|
||||
description={
|
||||
<span>
|
||||
Use <pre className="inline rounded bg-gray-100 px-2 py-1">P</pre> shortcut to
|
||||
Use <pre className="inline rounded bg-gray-200 px-2 py-1">P</pre> shortcut to
|
||||
create a new project
|
||||
</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// lib
|
||||
import type { NextPage, GetServerSideProps } from "next";
|
||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
||||
// constants
|
||||
// services
|
||||
import workspaceService from "services/workspace.service";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// ui
|
||||
import { Button } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// types
|
||||
import type { NextPage, GetServerSideProps } from "next";
|
||||
// fetch-keys
|
||||
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
type TBillingSettingsProps = {
|
||||
|
|
@ -35,9 +37,9 @@ const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<SettingsLayout
|
||||
memberType={{ ...props }}
|
||||
type="workspace"
|
||||
<AppLayout
|
||||
settingsLayout="workspace"
|
||||
memberType={props}
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
|
|
@ -75,7 +77,7 @@ const BillingSettings: NextPage<TBillingSettingsProps> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,173 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
|
||||
// lib
|
||||
import type { GetServerSideProps, NextPage } from "next";
|
||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
||||
// constants
|
||||
// services
|
||||
import workspaceService from "services/workspace.service";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
// ui
|
||||
import { Button } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
type TFeatureSettingsProps = {
|
||||
isOwner: boolean;
|
||||
isMember: boolean;
|
||||
isViewer: boolean;
|
||||
isGuest: boolean;
|
||||
};
|
||||
|
||||
const FeaturesSettings: NextPage<TFeatureSettingsProps> = (props) => {
|
||||
const {
|
||||
query: { workspaceSlug },
|
||||
} = useRouter();
|
||||
|
||||
const { data: activeWorkspace } = useSWR(
|
||||
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null,
|
||||
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsLayout
|
||||
memberType={{
|
||||
...props,
|
||||
}}
|
||||
type="workspace"
|
||||
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">Workspace Features</h3>
|
||||
</div>
|
||||
<div className="space-y-8 md:w-2/3">
|
||||
<div className="flex items-center gap-x-10 gap-y-2">
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Use modules</h4>
|
||||
<p className="mb-3 text-sm text-gray-500">
|
||||
Modules are enabled for all the projects in this workspace. Access it from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{/* Disabled- bg-gray-200, translate-x-0 */}
|
||||
<button
|
||||
type="button"
|
||||
className="pointer-events-none relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent bg-indigo-500 transition-colors duration-200 ease-in-out focus:outline-none"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none inline-block h-5 w-5 translate-x-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-10 gap-y-2">
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Use cycles</h4>
|
||||
<p className="mb-3 text-sm text-gray-500">
|
||||
Cycles are enabled for all the projects in this workspace. Access it from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{/* Disabled- bg-gray-200, translate-x-0 */}
|
||||
<button
|
||||
type="button"
|
||||
className="pointer-events-none relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent bg-indigo-500 transition-colors duration-200 ease-in-out focus:outline-none"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none inline-block h-5 w-5 translate-x-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-10 gap-y-2">
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6 text-gray-900">Use backlogs</h4>
|
||||
<p className="mb-3 text-sm text-gray-500">
|
||||
Backlog are enabled for all the projects in this workspace. Access it from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{/* Disabled- bg-gray-200, translate-x-0 */}
|
||||
<button
|
||||
type="button"
|
||||
className="pointer-events-none relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent bg-indigo-500 transition-colors duration-200 ease-in-out focus:outline-none"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none inline-block h-5 w-5 translate-x-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<a href="https://plane.so/" target="_blank" rel="noreferrer">
|
||||
<Button theme="secondary" size="rg" className="text-xs">
|
||||
Plane is open-source, view Roadmap
|
||||
</Button>
|
||||
</a>
|
||||
<a href="https://github.com/makeplane/plane" target="_blank" rel="noreferrer">
|
||||
<Button theme="secondary" size="rg" className="text-xs">
|
||||
Star us on GitHub
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</SettingsLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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 FeaturesSettings;
|
||||
|
|
@ -17,11 +17,11 @@ import { requiredWorkspaceAdmin } from "lib/auth";
|
|||
import workspaceService from "services/workspace.service";
|
||||
import fileServices from "services/file.service";
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { ImageUploadModal } from "components/common/image-upload-modal";
|
||||
import { ImageUploadModal } from "components/core";
|
||||
import ConfirmWorkspaceDeletion from "components/workspace/confirm-workspace-deletion";
|
||||
// ui
|
||||
import { Spinner, Button, Input, CustomSelect } from "components/ui";
|
||||
|
|
@ -35,7 +35,7 @@ import type { GetServerSideProps, NextPage } from "next";
|
|||
// fetch-keys
|
||||
import { WORKSPACE_DETAILS, USER_WORKSPACES } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { companySize } from "constants/";
|
||||
import { COMPANY_SIZE } from "constants/workspace";
|
||||
|
||||
const defaultValues: Partial<IWorkspace> = {
|
||||
name: "",
|
||||
|
|
@ -85,11 +85,13 @@ const WorkspaceSettings: NextPage<TWorkspaceSettingsProps> = (props) => {
|
|||
|
||||
const onSubmit = async (formData: IWorkspace) => {
|
||||
if (!activeWorkspace) return;
|
||||
|
||||
const payload: Partial<IWorkspace> = {
|
||||
logo: formData.logo,
|
||||
name: formData.name,
|
||||
company_size: formData.company_size,
|
||||
};
|
||||
|
||||
await workspaceService
|
||||
.updateWorkspace(activeWorkspace.slug, payload)
|
||||
.then((res) => {
|
||||
|
|
@ -106,9 +108,9 @@ const WorkspaceSettings: NextPage<TWorkspaceSettingsProps> = (props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<SettingsLayout
|
||||
memberType={{ ...props }}
|
||||
type="workspace"
|
||||
<AppLayout
|
||||
settingsLayout="workspace"
|
||||
memberType={props}
|
||||
meta={{
|
||||
title: "Plane - Workspace Settings",
|
||||
}}
|
||||
|
|
@ -278,7 +280,7 @@ const WorkspaceSettings: NextPage<TWorkspaceSettingsProps> = (props) => {
|
|||
label={value ? value.toString() : "Select company size"}
|
||||
input
|
||||
>
|
||||
{companySize?.map((item) => (
|
||||
{COMPANY_SIZE?.map((item) => (
|
||||
<CustomSelect.Option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</CustomSelect.Option>
|
||||
|
|
@ -315,7 +317,7 @@ const WorkspaceSettings: NextPage<TWorkspaceSettingsProps> = (props) => {
|
|||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,29 +2,31 @@ import { useState } from "react";
|
|||
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
// lib
|
||||
import type { GetServerSideProps, NextPage } from "next";
|
||||
import { requiredWorkspaceAdmin } from "lib/auth";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import workspaceService from "services/workspace.service";
|
||||
// constants
|
||||
// layouts
|
||||
import SettingsLayout from "layouts/settings-layout";
|
||||
import AppLayout from "layouts/app-layout";
|
||||
// components
|
||||
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
|
||||
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
|
||||
// ui
|
||||
import { Button, CustomListbox, CustomMenu, Loader } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
import { ROLE } from "constants/";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import type { GetServerSideProps, NextPage } from "next";
|
||||
// fetch-keys
|
||||
import { WORKSPACE_DETAILS, WORKSPACE_INVITATIONS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { ROLE } from "constants/workspace";
|
||||
|
||||
type TMembersSettingsProps = {
|
||||
isOwner: boolean;
|
||||
|
|
@ -135,11 +137,9 @@ const MembersSettings: NextPage<TMembersSettingsProps> = (props) => {
|
|||
workspace_slug={workspaceSlug as string}
|
||||
members={members}
|
||||
/>
|
||||
<SettingsLayout
|
||||
memberType={{
|
||||
...props,
|
||||
}}
|
||||
type="workspace"
|
||||
<AppLayout
|
||||
settingsLayout="workspace"
|
||||
memberType={props}
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem
|
||||
|
|
@ -283,7 +283,7 @@ const MembersSettings: NextPage<TMembersSettingsProps> = (props) => {
|
|||
</div>
|
||||
)}
|
||||
</section>
|
||||
</SettingsLayout>
|
||||
</AppLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,15 +10,13 @@ import type { AppProps } from "next/app";
|
|||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<>
|
||||
<UserProvider>
|
||||
<ToastContextProvider>
|
||||
<ThemeContextProvider>
|
||||
<Component {...pageProps} />
|
||||
</ThemeContextProvider>
|
||||
</ToastContextProvider>
|
||||
</UserProvider>
|
||||
</>
|
||||
<UserProvider>
|
||||
<ToastContextProvider>
|
||||
<ThemeContextProvider>
|
||||
<Component {...pageProps} />
|
||||
</ThemeContextProvider>
|
||||
</ToastContextProvider>
|
||||
</UserProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
33
apps/app/pages/_error.js
Normal file
33
apps/app/pages/_error.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher.
|
||||
*
|
||||
* NOTE: If using this with `next` version 12.2.0 or lower, uncomment the
|
||||
* penultimate line in `CustomErrorComponent`.
|
||||
*
|
||||
* This page is loaded by Nextjs:
|
||||
* - on the server, when data-fetching methods throw or reject
|
||||
* - on the client, when `getInitialProps` throws or rejects
|
||||
* - on the client, when a React lifecycle method throws or rejects, and it's
|
||||
* caught by the built-in Nextjs error boundary
|
||||
*
|
||||
* See:
|
||||
* - https://nextjs.org/docs/basic-features/data-fetching/overview
|
||||
* - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props
|
||||
* - https://reactjs.org/docs/error-boundaries.html
|
||||
*/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import NextErrorComponent from "next/error";
|
||||
|
||||
const CustomErrorComponent = (props) => <NextErrorComponent statusCode={props.statusCode} />;
|
||||
|
||||
CustomErrorComponent.getInitialProps = async (contextData) => {
|
||||
// In case this is running in a serverless function, await this in order to give Sentry
|
||||
// time to send the error before the lambda exits
|
||||
await Sentry.captureUnderscoreErrorException(contextData);
|
||||
|
||||
// This will contain the status code of the response
|
||||
return NextErrorComponent.getInitialProps(contextData);
|
||||
};
|
||||
|
||||
export default CustomErrorComponent;
|
||||
|
|
@ -25,7 +25,7 @@ import type { NextPage, NextPageContext } from "next";
|
|||
// fetch-keys
|
||||
import { USER_WORKSPACES } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { companySize } from "constants/";
|
||||
import { COMPANY_SIZE } from "constants/workspace";
|
||||
|
||||
const defaultValues = {
|
||||
name: "",
|
||||
|
|
@ -145,7 +145,7 @@ const CreateWorkspace: NextPage = () => {
|
|||
label={value ? value.toString() : "Select company size"}
|
||||
input
|
||||
>
|
||||
{companySize?.map((item) => (
|
||||
{COMPANY_SIZE?.map((item) => (
|
||||
<CustomSelect.Option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</CustomSelect.Option>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import React from "react";
|
||||
|
||||
import type { NextPage } from "next";
|
||||
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
|
||||
const ErrorPage: NextPage = () => (
|
||||
<DefaultLayout
|
||||
meta={{
|
||||
title: "Plane - An error occurred",
|
||||
description: "We were unable to get this page for you.",
|
||||
}}
|
||||
>
|
||||
<div className="h-full w-full">
|
||||
<h2 className="text-3xl">Error!</h2>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
);
|
||||
<DefaultLayout
|
||||
meta={{
|
||||
title: "Plane - An error occurred",
|
||||
description: "We were unable to get this page for you.",
|
||||
}}
|
||||
>
|
||||
<div className="h-full w-full">
|
||||
<h2 className="text-3xl">Error!</h2>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
);
|
||||
|
||||
export default ErrorPage;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { NextPage, NextPageContext } from "next";
|
||||
|
||||
// lib
|
||||
import { homePageRedirect } from "lib/auth";
|
||||
// types
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
|
||||
const Home: NextPage = () => null;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import { useRouter } from "next/router";
|
|||
|
||||
import { mutate } from "swr";
|
||||
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// services
|
||||
import type { NextPage } from "next";
|
||||
import authenticationService from "services/authentication.service";
|
||||
// constants
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useToast from "hooks/use-toast";
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
// constants
|
||||
import { USER_WORKSPACES } from "constants/fetch-keys";
|
||||
|
||||
const MagicSignIn: NextPage = () => {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import { useState } from "react";
|
|||
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
import useUser from "hooks/use-user";
|
||||
|
||||
// lib
|
||||
import { requiredAuth } from "lib/auth";
|
||||
// services
|
||||
import userService from "services/user.service";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// components
|
||||
|
|
@ -20,7 +22,8 @@ import InviteMembers from "components/onboarding/invite-members";
|
|||
import CommandMenu from "components/onboarding/command-menu";
|
||||
// images
|
||||
import Logo from "public/onboarding/logo.svg";
|
||||
import userService from "services/user.service";
|
||||
// types
|
||||
import type { NextPage, NextPageContext } from "next";
|
||||
|
||||
const Onboarding: NextPage = () => {
|
||||
const [step, setStep] = useState(1);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from "@heroicons/react/24/outline";
|
||||
// swr
|
||||
// services
|
||||
import type { NextPage } from "next";
|
||||
import workspaceService from "services/workspace.service";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
|
|
@ -23,6 +22,8 @@ import DefaultLayout from "layouts/default-layout";
|
|||
import { Spinner } from "components/ui";
|
||||
// icons
|
||||
import { EmptySpace, EmptySpaceItem } from "components/ui/empty-space";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
// constants
|
||||
import { WORKSPACE_INVITATION } from "constants/fetch-keys";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue