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:
sriram veeraghanta 2023-02-08 10:15:18 +05:30 committed by GitHub
parent 6966666bf5
commit d3b73dc32f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
177 changed files with 4767 additions and 5404 deletions

View file

@ -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

View file

@ -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>
) : (

View file

@ -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>
</>
);
};

View file

@ -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: "",

View file

@ -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 ?? []}
/>

View file

@ -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>
}

View file

@ -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}

View file

@ -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>

View file

@ -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}

View file

@ -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>
}

View file

@ -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>
);
};

View file

@ -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;

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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>
</>
);
};

View file

@ -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>
</>
);
};

View file

@ -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>
}

View file

@ -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>
</>
);
};

View file

@ -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;

View file

@ -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>
);
};

View file

@ -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>
</>
);
};

View file

@ -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
View 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;

View file

@ -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>

View file

@ -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;

View file

@ -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;

View file

@ -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 = () => {

View file

@ -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);

View file

@ -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";