Merge pull request #201 from makeplane/feat/user_auth

feat: added user auth
This commit is contained in:
sriram veeraghanta 2023-01-29 12:23:10 +05:30 committed by GitHub
commit d108dd28a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 356 additions and 299 deletions

View File

@ -4,7 +4,7 @@ import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// swr // swr
import useSWR from "swr"; import useSWR, { mutate } from "swr";
// react-beautiful-dnd // react-beautiful-dnd
import { DraggableStateSnapshot } from "react-beautiful-dnd"; import { DraggableStateSnapshot } from "react-beautiful-dnd";
// headless ui // headless ui
@ -17,48 +17,49 @@ import issuesService from "services/issues.service";
import stateService from "services/state.service"; import stateService from "services/state.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
// components // components
import { AssigneesList } from "components/ui/avatar"; import { CustomSelect, AssigneesList } from "components/ui";
// helpers // helpers
import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper";
// types // types
import { IIssue, IssueResponse, IUserLite, IWorkspaceMember, Properties } from "types"; import { IIssue, IUserLite, IWorkspaceMember, Properties, UserAuth } from "types";
// common // common
import { PRIORITIES } from "constants/"; import { PRIORITIES } from "constants/";
import { PROJECT_ISSUES_LIST, STATE_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import {
STATE_LIST,
PROJECT_DETAILS,
CYCLE_ISSUES,
MODULE_ISSUES,
PROJECT_ISSUES_LIST,
} from "constants/fetch-keys";
import { getPriorityIcon } from "constants/global"; import { getPriorityIcon } from "constants/global";
type Props = { type Props = {
type?: string;
typeId?: string;
issue: IIssue; issue: IIssue;
properties: Properties; properties: Properties;
snapshot?: DraggableStateSnapshot; snapshot?: DraggableStateSnapshot;
assignees: Partial<IUserLite>[] | (Partial<IUserLite> | undefined)[]; assignees: Partial<IUserLite>[] | (Partial<IUserLite> | undefined)[];
people: IWorkspaceMember[] | undefined; people: IWorkspaceMember[] | undefined;
handleDeleteIssue?: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue?: React.Dispatch<React.SetStateAction<string | undefined>>;
partialUpdateIssue: any; userAuth: UserAuth;
}; };
const SingleBoardIssue: React.FC<Props> = ({ const SingleBoardIssue: React.FC<Props> = ({
type,
typeId,
issue, issue,
properties, properties,
snapshot, snapshot,
assignees, assignees,
people, people,
handleDeleteIssue, handleDeleteIssue,
partialUpdateIssue, userAuth,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { data: issues } = useSWR<IssueResponse>(
workspaceSlug && projectId
? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)
: null,
workspaceSlug && projectId
? () => issuesService.getIssues(workspaceSlug as string, projectId as string)
: null
);
const { data: states } = useSWR( const { data: states } = useSWR(
workspaceSlug && projectId ? STATE_LIST(projectId as string) : null, workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId
@ -73,7 +74,25 @@ const SingleBoardIssue: React.FC<Props> = ({
: null : null
); );
const totalChildren = issues?.results.filter((i) => i.parent === issue.id).length; const partialUpdateIssue = (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId) return;
issuesService
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
.then((res) => {
if (typeId) {
mutate(CYCLE_ISSUES(typeId ?? ""));
mutate(MODULE_ISSUES(typeId ?? ""));
}
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
})
.catch((error) => {
console.log(error);
});
};
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
return ( return (
<div <div
@ -82,7 +101,7 @@ const SingleBoardIssue: React.FC<Props> = ({
}`} }`}
> >
<div className="group/card relative select-none p-2"> <div className="group/card relative select-none p-2">
{handleDeleteIssue && ( {handleDeleteIssue && !isNotAllowed && (
<div className="absolute top-1.5 right-1.5 z-10 opacity-0 group-hover/card:opacity-100"> <div className="absolute top-1.5 right-1.5 z-10 opacity-0 group-hover/card:opacity-100">
<button <button
type="button" type="button"
@ -114,15 +133,18 @@ const SingleBoardIssue: React.FC<Props> = ({
as="div" as="div"
value={issue.priority} value={issue.priority}
onChange={(data: string) => { onChange={(data: string) => {
partialUpdateIssue({ priority: data }, issue.id); partialUpdateIssue({ priority: data });
}} }}
className="group relative flex-shrink-0" className="group relative flex-shrink-0"
disabled={isNotAllowed}
> >
{({ open }) => ( {({ open }) => (
<> <>
<div> <div>
<Listbox.Button <Listbox.Button
className={`grid cursor-pointer place-items-center rounded px-2 py-1 capitalize shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ className={`grid ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
} place-items-center rounded px-2 py-1 capitalize shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
issue.priority === "urgent" issue.priority === "urgent"
? "bg-red-100 text-red-600" ? "bg-red-100 text-red-600"
: issue.priority === "high" : issue.priority === "high"
@ -171,14 +193,19 @@ const SingleBoardIssue: React.FC<Props> = ({
as="div" as="div"
value={issue.state} value={issue.state}
onChange={(data: string) => { onChange={(data: string) => {
partialUpdateIssue({ state: data }, issue.id); partialUpdateIssue({ state: data });
}} }}
className="group relative flex-shrink-0" className="group relative flex-shrink-0"
disabled={isNotAllowed}
> >
{({ open }) => ( {({ open }) => (
<> <>
<div> <div>
<Listbox.Button className="flex cursor-pointer items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"> <Listbox.Button
className={`flex ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
} items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500`}
>
<span <span
className="h-1.5 w-1.5 flex-shrink-0 rounded-full" className="h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{ style={{
@ -218,10 +245,6 @@ const SingleBoardIssue: React.FC<Props> = ({
</Listbox.Options> </Listbox.Options>
</Transition> </Transition>
</div> </div>
{/* <div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1">State</h5>
<div>{issue.state_detail.name}</div>
</div> */}
</> </>
)} )}
</Listbox> </Listbox>
@ -242,7 +265,7 @@ const SingleBoardIssue: React.FC<Props> = ({
)} )}
{properties.sub_issue_count && ( {properties.sub_issue_count && (
<div className="flex flex-shrink-0 items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"> <div className="flex flex-shrink-0 items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
{totalChildren} {totalChildren === 1 ? "sub-issue" : "sub-issues"} {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
</div> </div>
)} )}
{properties.assignee && ( {properties.assignee && (
@ -255,81 +278,82 @@ const SingleBoardIssue: React.FC<Props> = ({
if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);
else newData.push(data); else newData.push(data);
partialUpdateIssue({ assignees_list: newData }, issue.id); partialUpdateIssue({ assignees_list: newData });
}} }}
className="group relative flex-shrink-0" className="group relative flex-shrink-0"
disabled={isNotAllowed}
> >
{({ open }) => ( {({ open }) => (
<> <div>
<div> <Listbox.Button>
<Listbox.Button> <div
<div className="flex cursor-pointer items-center gap-1 text-xs"> className={`flex ${
<AssigneesList users={assignees} length={3} /> isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
</div> } items-center gap-1 text-xs`}
</Listbox.Button>
<Transition
show={open}
as={React.Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
> >
<Listbox.Options className="absolute left-0 z-20 mt-1 max-h-28 overflow-auto rounded-md bg-white py-1 text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"> <AssigneesList users={assignees} length={3} />
{people?.map((person) => ( </div>
<Listbox.Option </Listbox.Button>
key={person.id}
className={({ active }) => <Transition
`cursor-pointer select-none p-2 ${ show={open}
active ? "bg-indigo-50" : "bg-white" as={React.Fragment}
}` leave="transition ease-in duration-100"
} leaveFrom="opacity-100"
value={person.member.id} leaveTo="opacity-0"
>
<Listbox.Options className="absolute left-0 z-20 mt-1 max-h-28 overflow-auto rounded-md bg-white py-1 text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
{people?.map((person) => (
<Listbox.Option
key={person.id}
className={({ active }) =>
`cursor-pointer select-none p-2 ${active ? "bg-indigo-50" : "bg-white"}`
}
value={person.member.id}
>
<div
className={`flex items-center gap-x-1 ${
assignees.includes({
id: person.member.last_name,
first_name: person.member.first_name,
last_name: person.member.last_name,
email: person.member.email,
avatar: person.member.avatar,
})
? "font-medium"
: "font-normal"
}`}
> >
<div {person.member.avatar && person.member.avatar !== "" ? (
className={`flex items-center gap-x-1 ${ <div className="relative h-4 w-4">
assignees.includes({ <Image
id: person.member.last_name, src={person.member.avatar}
first_name: person.member.first_name, alt="avatar"
last_name: person.member.last_name, className="rounded-full"
email: person.member.email, layout="fill"
avatar: person.member.avatar, objectFit="cover"
}) priority={false}
? "font-medium" loading="lazy"
: "font-normal" />
}`} </div>
> ) : (
{person.member.avatar && person.member.avatar !== "" ? ( <div className="grid h-4 w-4 place-items-center rounded-full bg-gray-700 capitalize text-white">
<div className="relative h-4 w-4">
<Image
src={person.member.avatar}
alt="avatar"
className="rounded-full"
layout="fill"
objectFit="cover"
priority={false}
loading="lazy"
/>
</div>
) : (
<div className="grid h-4 w-4 place-items-center rounded-full bg-gray-700 capitalize text-white">
{person.member.first_name && person.member.first_name !== ""
? person.member.first_name.charAt(0)
: person.member.email.charAt(0)}
</div>
)}
<p>
{person.member.first_name && person.member.first_name !== "" {person.member.first_name && person.member.first_name !== ""
? person.member.first_name ? person.member.first_name.charAt(0)
: person.member.email} : person.member.email.charAt(0)}
</p> </div>
</div> )}
</Listbox.Option> <p>
))} {person.member.first_name && person.member.first_name !== ""
</Listbox.Options> ? person.member.first_name
</Transition> : person.member.email}
</div> </p>
</> </div>
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
)} )}
</Listbox> </Listbox>
)} )}

View File

@ -21,7 +21,7 @@ import { CalendarDaysIcon } from "@heroicons/react/24/outline";
import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper";
// types // types
import { IIssue, IWorkspaceMember, Properties } from "types"; import { IIssue, IWorkspaceMember, Properties, UserAuth } from "types";
// fetch-keys // fetch-keys
import { import {
CYCLE_ISSUES, CYCLE_ISSUES,
@ -41,6 +41,7 @@ type Props = {
properties: Properties; properties: Properties;
editIssue: () => void; editIssue: () => void;
removeIssue?: () => void; removeIssue?: () => void;
userAuth: UserAuth;
}; };
const SingleListIssue: React.FC<Props> = ({ const SingleListIssue: React.FC<Props> = ({
@ -50,6 +51,7 @@ const SingleListIssue: React.FC<Props> = ({
properties, properties,
editIssue, editIssue,
removeIssue, removeIssue,
userAuth,
}) => { }) => {
const [deleteIssue, setDeleteIssue] = useState<IIssue | undefined>(); const [deleteIssue, setDeleteIssue] = useState<IIssue | undefined>();
@ -86,6 +88,8 @@ const SingleListIssue: React.FC<Props> = ({
}); });
}; };
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
return ( return (
<> <>
<ConfirmIssueDeletion <ConfirmIssueDeletion
@ -121,12 +125,15 @@ const SingleListIssue: React.FC<Props> = ({
partialUpdateIssue({ priority: data }); partialUpdateIssue({ priority: data });
}} }}
className="group relative flex-shrink-0" className="group relative flex-shrink-0"
disabled={isNotAllowed}
> >
{({ open }) => ( {({ open }) => (
<> <>
<div> <div>
<Listbox.Button <Listbox.Button
className={`flex cursor-pointer items-center gap-x-2 rounded px-2 py-0.5 capitalize shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ className={`flex ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
} items-center gap-x-2 rounded px-2 py-0.5 capitalize shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
issue.priority === "urgent" issue.priority === "urgent"
? "bg-red-100 text-red-600" ? "bg-red-100 text-red-600"
: issue.priority === "high" : issue.priority === "high"
@ -210,6 +217,7 @@ const SingleListIssue: React.FC<Props> = ({
}} }}
maxHeight="md" maxHeight="md"
noChevron noChevron
disabled={isNotAllowed}
> >
{states?.map((state) => ( {states?.map((state) => (
<CustomSelect.Option key={state.id} value={state.id}> <CustomSelect.Option key={state.id} value={state.id}>
@ -270,12 +278,17 @@ const SingleListIssue: React.FC<Props> = ({
partialUpdateIssue({ assignees_list: newData }); partialUpdateIssue({ assignees_list: newData });
}} }}
className="group relative flex-shrink-0" className="group relative flex-shrink-0"
disabled={isNotAllowed}
> >
{({ open }) => ( {({ open }) => (
<> <>
<div> <div>
<Listbox.Button> <Listbox.Button>
<div className="flex cursor-pointer items-center gap-1 text-xs"> <div
className={`flex ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
} items-center gap-1 text-xs`}
>
<AssigneesList userIds={issue.assignees ?? []} /> <AssigneesList userIds={issue.assignees ?? []} />
</div> </div>
</Listbox.Button> </Listbox.Button>
@ -325,7 +338,7 @@ const SingleListIssue: React.FC<Props> = ({
)} )}
</Listbox> </Listbox>
)} )}
{type && ( {type && !isNotAllowed && (
<CustomMenu width="auto" ellipsis> <CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={editIssue}>Edit</CustomMenu.MenuItem> <CustomMenu.MenuItem onClick={editIssue}>Edit</CustomMenu.MenuItem>
{type !== "issue" && ( {type !== "issue" && (

View File

@ -2,19 +2,19 @@ import React, { useEffect, useRef, useState } from "react";
import { mutate } from "swr"; import { mutate } from "swr";
// headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// services // services
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import type { IProject, IWorkspace } from "types";
import projectService from "services/project.service"; import projectService from "services/project.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// icons // icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui // ui
import { Button, Input } from "components/ui"; import { Button, Input } from "components/ui";
// types // types
// constants import type { IProject, IWorkspace } from "types";
// fetch-keys
import { PROJECTS_LIST } from "constants/fetch-keys"; import { PROJECTS_LIST } from "constants/fetch-keys";
type TConfirmProjectDeletionProps = { type TConfirmProjectDeletionProps = {
@ -86,7 +86,7 @@ const ConfirmProjectDeletion: React.FC<TConfirmProjectDeletionProps> = (props) =
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-20"
initialFocus={cancelButtonRef} initialFocus={cancelButtonRef}
onClose={handleClose} onClose={handleClose}
> >
@ -102,7 +102,7 @@ const ConfirmProjectDeletion: React.FC<TConfirmProjectDeletionProps> = (props) =
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}

View File

@ -33,7 +33,7 @@ const ConfirmProjectMemberRemove: React.FC<Props> = ({ isOpen, onClose, data, ha
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-20"
initialFocus={cancelButtonRef} initialFocus={cancelButtonRef}
onClose={handleClose} onClose={handleClose}
> >
@ -49,7 +49,7 @@ const ConfirmProjectMemberRemove: React.FC<Props> = ({ isOpen, onClose, data, ha
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}

View File

@ -13,7 +13,7 @@ import SingleBoard from "components/project/cycles/board-view/single-board";
// ui // ui
import { Spinner } from "components/ui"; import { Spinner } from "components/ui";
// types // types
import { CycleIssueResponse, IIssue, IProjectMember } from "types"; import { CycleIssueResponse, IIssue, IProjectMember, UserAuth } from "types";
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
// constants // constants
import { STATE_LIST, CYCLE_ISSUES } from "constants/fetch-keys"; import { STATE_LIST, CYCLE_ISSUES } from "constants/fetch-keys";
@ -23,8 +23,6 @@ type Props = {
members: IProjectMember[] | undefined; members: IProjectMember[] | undefined;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void; openIssuesListModal: () => void;
removeIssueFromCycle: (bridgeId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch< setPreloadedData: React.Dispatch<
React.SetStateAction< React.SetStateAction<
@ -34,6 +32,7 @@ type Props = {
| null | null
> >
>; >;
userAuth: UserAuth;
}; };
const CyclesBoardView: React.FC<Props> = ({ const CyclesBoardView: React.FC<Props> = ({
@ -41,10 +40,9 @@ const CyclesBoardView: React.FC<Props> = ({
members, members,
openCreateIssueModal, openCreateIssueModal,
openIssuesListModal, openIssuesListModal,
removeIssueFromCycle,
partialUpdateIssue,
handleDeleteIssue, handleDeleteIssue,
setPreloadedData, setPreloadedData,
userAuth,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
@ -151,10 +149,8 @@ const CyclesBoardView: React.FC<Props> = ({
: "#000000" : "#000000"
} }
properties={properties} properties={properties}
removeIssueFromCycle={removeIssueFromCycle}
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
openCreateIssueModal={openCreateIssueModal} openCreateIssueModal={openCreateIssueModal}
partialUpdateIssue={partialUpdateIssue}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
setPreloadedData={setPreloadedData} setPreloadedData={setPreloadedData}
stateId={ stateId={
@ -162,6 +158,7 @@ const CyclesBoardView: React.FC<Props> = ({
? states?.find((s) => s.name === singleGroup)?.id ?? null ? states?.find((s) => s.name === singleGroup)?.id ?? null
: null : null
} }
userAuth={userAuth}
/> />
))} ))}
</div> </div>

View File

@ -17,7 +17,7 @@ import { CustomMenu } from "components/ui";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import { IIssue, IWorkspaceMember, NestedKeyOf, Properties } from "types"; import { IIssue, IWorkspaceMember, NestedKeyOf, Properties, UserAuth } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
@ -32,8 +32,6 @@ type Props = {
bgColor?: string; bgColor?: string;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void; openIssuesListModal: () => void;
removeIssueFromCycle: (bridgeId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch< setPreloadedData: React.Dispatch<
React.SetStateAction< React.SetStateAction<
@ -44,6 +42,7 @@ type Props = {
> >
>; >;
stateId: string | null; stateId: string | null;
userAuth: UserAuth;
}; };
const SingleModuleBoard: React.FC<Props> = ({ const SingleModuleBoard: React.FC<Props> = ({
@ -55,18 +54,17 @@ const SingleModuleBoard: React.FC<Props> = ({
bgColor, bgColor,
openCreateIssueModal, openCreateIssueModal,
openIssuesListModal, openIssuesListModal,
removeIssueFromCycle,
partialUpdateIssue,
handleDeleteIssue, handleDeleteIssue,
setPreloadedData, setPreloadedData,
stateId, stateId,
userAuth,
}) => { }) => {
// collapse/expand // collapse/expand
const [isCollapsed, setIsCollapsed] = useState(true); const [isCollapsed, setIsCollapsed] = useState(true);
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug, cycleId } = router.query;
if (selectedGroup === "priority") if (selectedGroup === "priority")
groupTitle === "high" groupTitle === "high"
@ -132,13 +130,15 @@ const SingleModuleBoard: React.FC<Props> = ({
{...provided.dragHandleProps} {...provided.dragHandleProps}
> >
<SingleIssue <SingleIssue
type="cycle"
typeId={cycleId as string}
issue={childIssue} issue={childIssue}
properties={properties} properties={properties}
snapshot={snapshot} snapshot={snapshot}
assignees={assignees} assignees={assignees}
people={people} people={people}
partialUpdateIssue={partialUpdateIssue}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
userAuth={userAuth}
/> />
</div> </div>
)} )}

View File

@ -7,14 +7,14 @@ import { mutate } from "swr";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// headless // headless
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// types
import type { ICycle } from "types";
// services // services
import cycleService from "services/cycles.service"; import cycleService from "services/cycles.service";
import { Button, Input, TextArea, CustomSelect } from "components/ui";
// ui // ui
import { Button, Input, TextArea, CustomSelect } from "components/ui";
// common // common
import { renderDateFormat } from "helpers/date-time.helper"; import { renderDateFormat } from "helpers/date-time.helper";
// types
import type { ICycle } from "types";
// fetch keys // fetch keys
import { CYCLE_LIST } from "constants/fetch-keys"; import { CYCLE_LIST } from "constants/fetch-keys";
@ -86,11 +86,11 @@ const CreateUpdateCycleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, proj
CYCLE_LIST(projectId), CYCLE_LIST(projectId),
(prevData) => { (prevData) => {
const newData = prevData?.map((item) => { const newData = prevData?.map((item) => {
if (item.id === res.id) { if (item.id === res.id) return res;
return res;
}
return item; return item;
}); });
return newData; return newData;
}, },
false false

View File

@ -21,7 +21,7 @@ import { CustomMenu, Spinner } from "components/ui";
// helpers // helpers
import { addSpaceIfCamelCase } from "helpers/string.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper";
// types // types
import { IIssue, IWorkspaceMember } from "types"; import { IIssue, IWorkspaceMember, UserAuth } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_MEMBERS, STATE_LIST } from "constants/fetch-keys"; import { WORKSPACE_MEMBERS, STATE_LIST } from "constants/fetch-keys";
@ -38,6 +38,7 @@ type Props = {
| null | null
> >
>; >;
userAuth: UserAuth;
}; };
const CyclesListView: React.FC<Props> = ({ const CyclesListView: React.FC<Props> = ({
@ -46,6 +47,7 @@ const CyclesListView: React.FC<Props> = ({
openIssuesListModal, openIssuesListModal,
removeIssueFromCycle, removeIssueFromCycle,
setPreloadedData, setPreloadedData,
userAuth,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
@ -140,6 +142,7 @@ const CyclesListView: React.FC<Props> = ({
properties={properties} properties={properties}
editIssue={() => openCreateIssueModal(issue, "edit")} editIssue={() => openCreateIssueModal(issue, "edit")}
removeIssue={() => removeIssueFromCycle(issue.bridge ?? "")} removeIssue={() => removeIssueFromCycle(issue.bridge ?? "")}
userAuth={userAuth}
/> />
); );
}) })

View File

@ -21,17 +21,17 @@ import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deleti
// ui // ui
import { Spinner } from "components/ui"; import { Spinner } from "components/ui";
// types // types
import type { IState, IIssue, IssueResponse } from "types"; import type { IState, IIssue, IssueResponse, UserAuth } from "types";
// fetch-keys // fetch-keys
import { STATE_LIST, PROJECT_ISSUES_LIST, PROJECT_MEMBERS } from "constants/fetch-keys"; import { STATE_LIST, PROJECT_ISSUES_LIST, PROJECT_MEMBERS } from "constants/fetch-keys";
type Props = { type Props = {
issues: IIssue[]; issues: IIssue[];
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void; userAuth: UserAuth;
}; };
const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, partialUpdateIssue }) => { const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) => {
const [createIssueModal, setCreateIssueModal] = useState(false); const [createIssueModal, setCreateIssueModal] = useState(false);
const [isIssueDeletionOpen, setIsIssueDeletionOpen] = useState(false); const [isIssueDeletionOpen, setIsIssueDeletionOpen] = useState(false);
const [issueDeletionData, setIssueDeletionData] = useState<IIssue | undefined>(); const [issueDeletionData, setIssueDeletionData] = useState<IIssue | undefined>();
@ -238,7 +238,7 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, partialUpdateIs
: "#000000" : "#000000"
} }
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
partialUpdateIssue={partialUpdateIssue} userAuth={userAuth}
/> />
))} ))}
</div> </div>

View File

@ -15,7 +15,7 @@ import { PlusIcon } from "@heroicons/react/24/outline";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// types // types
import { IIssue, Properties, NestedKeyOf, IWorkspaceMember } from "types"; import { IIssue, Properties, NestedKeyOf, IWorkspaceMember, UserAuth } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
@ -40,7 +40,7 @@ type Props = {
stateId: string | null; stateId: string | null;
createdBy: string | null; createdBy: string | null;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
partialUpdateIssue: (formData: Partial<IIssue>, childIssueId: string) => void; userAuth: UserAuth;
}; };
const SingleBoard: React.FC<Props> = ({ const SingleBoard: React.FC<Props> = ({
@ -55,7 +55,7 @@ const SingleBoard: React.FC<Props> = ({
stateId, stateId,
createdBy, createdBy,
handleDeleteIssue, handleDeleteIssue,
partialUpdateIssue, userAuth,
}) => { }) => {
// Collapse/Expand // Collapse/Expand
const [isCollapsed, setIsCollapsed] = useState(true); const [isCollapsed, setIsCollapsed] = useState(true);
@ -145,7 +145,7 @@ const SingleBoard: React.FC<Props> = ({
people={people} people={people}
assignees={assignees} assignees={assignees}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
partialUpdateIssue={partialUpdateIssue} userAuth={userAuth}
/> />
</div> </div>
)} )}

View File

@ -20,7 +20,7 @@ import SingleListIssue from "components/common/list-view/single-issue";
// helpers // helpers
import { addSpaceIfCamelCase } from "helpers/string.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper";
// types // types
import { IIssue, IWorkspaceMember } from "types"; import { IIssue, IWorkspaceMember, UserAuth } from "types";
// fetch-keys // fetch-keys
import { STATE_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { STATE_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
@ -28,10 +28,10 @@ import { STATE_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
type Props = { type Props = {
issues: IIssue[]; issues: IIssue[];
handleEditIssue: (issue: IIssue) => void; handleEditIssue: (issue: IIssue) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void; userAuth: UserAuth;
}; };
const ListView: React.FC<Props> = ({ issues, handleEditIssue }) => { const ListView: React.FC<Props> = ({ issues, handleEditIssue, userAuth }) => {
const [isCreateIssuesModalOpen, setIsCreateIssuesModalOpen] = useState(false); const [isCreateIssuesModalOpen, setIsCreateIssuesModalOpen] = useState(false);
const [preloadedData, setPreloadedData] = useState< const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined (Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
@ -130,6 +130,7 @@ const ListView: React.FC<Props> = ({ issues, handleEditIssue }) => {
issue={issue} issue={issue}
properties={properties} properties={properties}
editIssue={() => handleEditIssue(issue)} editIssue={() => handleEditIssue(issue)}
userAuth={userAuth}
/> />
); );
}) })

View File

@ -17,7 +17,7 @@ import SingleBoard from "components/project/modules/board-view/single-board";
// ui // ui
import { Spinner } from "components/ui"; import { Spinner } from "components/ui";
// types // types
import { IIssue, IProjectMember, ModuleIssueResponse } from "types"; import { IIssue, IProjectMember, ModuleIssueResponse, UserAuth } from "types";
// constants // constants
import { STATE_LIST, MODULE_ISSUES } from "constants/fetch-keys"; import { STATE_LIST, MODULE_ISSUES } from "constants/fetch-keys";
@ -26,8 +26,6 @@ type Props = {
members: IProjectMember[] | undefined; members: IProjectMember[] | undefined;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void; openIssuesListModal: () => void;
removeIssueFromModule: (issueId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch< setPreloadedData: React.Dispatch<
React.SetStateAction< React.SetStateAction<
@ -37,6 +35,7 @@ type Props = {
| null | null
> >
>; >;
userAuth: UserAuth;
}; };
const ModulesBoardView: React.FC<Props> = ({ const ModulesBoardView: React.FC<Props> = ({
@ -44,10 +43,9 @@ const ModulesBoardView: React.FC<Props> = ({
members, members,
openCreateIssueModal, openCreateIssueModal,
openIssuesListModal, openIssuesListModal,
removeIssueFromModule,
partialUpdateIssue,
handleDeleteIssue, handleDeleteIssue,
setPreloadedData, setPreloadedData,
userAuth,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
@ -154,10 +152,8 @@ const ModulesBoardView: React.FC<Props> = ({
: "#000000" : "#000000"
} }
properties={properties} properties={properties}
removeIssueFromModule={removeIssueFromModule}
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
openCreateIssueModal={openCreateIssueModal} openCreateIssueModal={openCreateIssueModal}
partialUpdateIssue={partialUpdateIssue}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
setPreloadedData={setPreloadedData} setPreloadedData={setPreloadedData}
stateId={ stateId={
@ -165,6 +161,7 @@ const ModulesBoardView: React.FC<Props> = ({
? states?.find((s) => s.name === singleGroup)?.id ?? null ? states?.find((s) => s.name === singleGroup)?.id ?? null
: null : null
} }
userAuth={userAuth}
/> />
))} ))}
</div> </div>

View File

@ -17,7 +17,7 @@ import { CustomMenu } from "components/ui";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import { IIssue, IWorkspaceMember, NestedKeyOf, Properties } from "types"; import { IIssue, IWorkspaceMember, NestedKeyOf, Properties, UserAuth } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
@ -32,8 +32,6 @@ type Props = {
bgColor?: string; bgColor?: string;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void; openIssuesListModal: () => void;
removeIssueFromModule: (bridgeId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch< setPreloadedData: React.Dispatch<
React.SetStateAction< React.SetStateAction<
@ -44,6 +42,7 @@ type Props = {
> >
>; >;
stateId: string | null; stateId: string | null;
userAuth: UserAuth;
}; };
const SingleModuleBoard: React.FC<Props> = ({ const SingleModuleBoard: React.FC<Props> = ({
@ -55,17 +54,16 @@ const SingleModuleBoard: React.FC<Props> = ({
bgColor, bgColor,
openCreateIssueModal, openCreateIssueModal,
openIssuesListModal, openIssuesListModal,
removeIssueFromModule,
partialUpdateIssue,
handleDeleteIssue, handleDeleteIssue,
setPreloadedData, setPreloadedData,
stateId, stateId,
userAuth,
}) => { }) => {
// Collapse/Expand // Collapse/Expand
const [isCollapsed, setIsCollapsed] = useState(true); const [isCollapsed, setIsCollapsed] = useState(true);
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug, moduleId } = router.query;
if (selectedGroup === "priority") if (selectedGroup === "priority")
groupTitle === "high" groupTitle === "high"
@ -112,10 +110,10 @@ const SingleModuleBoard: React.FC<Props> = ({
{...provided.droppableProps} {...provided.droppableProps}
ref={provided.innerRef} ref={provided.innerRef}
> >
{groupedByIssues[groupTitle].map((childIssue, index: number) => { {groupedByIssues[groupTitle].map((issue, index: number) => {
const assignees = [ const assignees = [
...(childIssue?.assignees_list ?? []), ...(issue?.assignees_list ?? []),
...(childIssue?.assignees ?? []), ...(issue?.assignees ?? []),
]?.map((assignee) => { ]?.map((assignee) => {
const tempPerson = people?.find((p) => p.member.id === assignee)?.member; const tempPerson = people?.find((p) => p.member.id === assignee)?.member;
@ -123,7 +121,7 @@ const SingleModuleBoard: React.FC<Props> = ({
}); });
return ( return (
<Draggable key={childIssue.id} draggableId={childIssue.id} index={index}> <Draggable key={issue.id} draggableId={issue.id} index={index}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
ref={provided.innerRef} ref={provided.innerRef}
@ -131,13 +129,15 @@ const SingleModuleBoard: React.FC<Props> = ({
{...provided.dragHandleProps} {...provided.dragHandleProps}
> >
<SingleIssue <SingleIssue
issue={childIssue} type="module"
typeId={moduleId as string}
issue={issue}
properties={properties} properties={properties}
snapshot={snapshot} snapshot={snapshot}
assignees={assignees} assignees={assignees}
people={people} people={people}
partialUpdateIssue={partialUpdateIssue}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
userAuth={userAuth}
/> />
</div> </div>
)} )}

View File

@ -18,7 +18,7 @@ import { CustomMenu, Spinner } from "components/ui";
// helpers // helpers
import { addSpaceIfCamelCase } from "helpers/string.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper";
// types // types
import { IIssue, IWorkspaceMember } from "types"; import { IIssue, IWorkspaceMember, UserAuth } from "types";
// fetch-keys // fetch-keys
import { STATE_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { STATE_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
@ -35,6 +35,7 @@ type Props = {
| null | null
> >
>; >;
userAuth: UserAuth;
}; };
const ModulesListView: React.FC<Props> = ({ const ModulesListView: React.FC<Props> = ({
@ -43,6 +44,7 @@ const ModulesListView: React.FC<Props> = ({
openIssuesListModal, openIssuesListModal,
removeIssueFromModule, removeIssueFromModule,
setPreloadedData, setPreloadedData,
userAuth,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
@ -137,6 +139,7 @@ const ModulesListView: React.FC<Props> = ({
properties={properties} properties={properties}
editIssue={() => openCreateIssueModal(issue, "edit")} editIssue={() => openCreateIssueModal(issue, "edit")}
removeIssue={() => removeIssueFromModule(issue.bridge ?? "")} removeIssue={() => removeIssueFromModule(issue.bridge ?? "")}
userAuth={userAuth}
/> />
); );
}) })

View File

@ -9,7 +9,7 @@ import { useForm, Controller } from "react-hook-form";
import { Dialog, Transition, Listbox } from "@headlessui/react"; import { Dialog, Transition, Listbox } from "@headlessui/react";
// ui // ui
import { ChevronDownIcon, CheckIcon } from "@heroicons/react/20/solid"; import { ChevronDownIcon, CheckIcon } from "@heroicons/react/20/solid";
import { Button, Select, TextArea } from "components/ui"; import { Button, CustomSelect, Select, TextArea } from "components/ui";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// services // services
@ -106,7 +106,7 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-10" onClose={handleClose}> <Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
@ -119,7 +119,7 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0"> <div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}
@ -196,40 +196,17 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
uninvitedPeople?.map((person) => ( uninvitedPeople?.map((person) => (
<Listbox.Option <Listbox.Option
key={person.member.id} key={person.member.id}
className={({ active }) => className={({ active, selected }) =>
`${ `${active ? "bg-indigo-50" : ""} ${
active ? "bg-theme text-white" : "text-gray-900" selected ? "bg-indigo-50 font-medium" : ""
} relative cursor-default select-none py-2 pl-3 pr-9 text-left` } text-gray-900 cursor-default select-none p-2`
} }
value={{ value={{
id: person.member.id, id: person.member.id,
email: person.member.email, email: person.member.email,
}} }}
> >
{({ selected, active }) => ( {person.member.email}
<>
<span
className={`${
selected ? "font-semibold" : "font-normal"
} block truncate`}
>
{person.member.email}
</span>
{selected ? (
<span
className={`absolute inset-y-0 right-0 flex items-center pr-4 ${
active ? "text-white" : "text-theme"
}`}
>
<CheckIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
) : null}
</>
)}
</Listbox.Option> </Listbox.Option>
)) ))
)} )}
@ -246,22 +223,28 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
/> />
</div> </div>
<div> <div>
<div> <h6 className="text-gray-500">Role</h6>
<Select <Controller
id="role" name="role"
label="Role" control={control}
name="role" render={({ field }) => (
error={errors.role} <CustomSelect
register={register} {...field}
validations={{ label={
required: "Role is required", <span className="capitalize">
}} {field.value ? ROLE[field.value] : "Select role"}
options={Object.entries(ROLE).map(([key, value]) => ({ </span>
value: key, }
label: value, input
}))} >
/> {Object.entries(ROLE).map(([key, label]) => (
</div> <CustomSelect.Option key={key} value={key}>
{label}
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div> </div>
<div> <div>
<TextArea <TextArea

View File

@ -14,6 +14,7 @@ type CustomSelectProps = {
width?: "auto" | string; width?: "auto" | string;
input?: boolean; input?: boolean;
noChevron?: boolean; noChevron?: boolean;
disabled?: boolean;
}; };
const CustomSelect = ({ const CustomSelect = ({
@ -26,11 +27,20 @@ const CustomSelect = ({
width = "auto", width = "auto",
input = false, input = false,
noChevron = false, noChevron = false,
disabled = false,
}: CustomSelectProps) => ( }: CustomSelectProps) => (
<Listbox as="div" value={value} onChange={onChange} className="relative flex-shrink-0 text-left"> <Listbox
as="div"
value={value}
onChange={onChange}
className="relative flex-shrink-0 text-left"
disabled={disabled}
>
<div> <div>
<Listbox.Button <Listbox.Button
className={`flex w-full cursor-pointer items-center justify-between gap-1 rounded-md border shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ className={`flex w-full ${
disabled ? "cursor-not-allowed" : "cursor-pointer"
} items-center justify-between gap-1 rounded-md border shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
input ? "border-gray-300 px-3 py-2 text-sm" : "px-2 py-1 text-xs" input ? "border-gray-300 px-3 py-2 text-sm" : "px-2 py-1 text-xs"
} ${ } ${
textAlignment === "right" textAlignment === "right"

View File

@ -4,19 +4,20 @@ import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
// headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// constants
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import type { IWorkspace } from "types";
import { USER_WORKSPACES } from "constants/fetch-keys";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// icons // icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui // ui
import { Button, Input } from "components/ui"; import { Button, Input } from "components/ui";
// types // types
import type { IWorkspace } from "types";
// fetch-keys
import { USER_WORKSPACES } from "constants/fetch-keys";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -78,7 +79,7 @@ const ConfirmWorkspaceDeletion: React.FC<Props> = ({ isOpen, data, onClose }) =>
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-20"
initialFocus={cancelButtonRef} initialFocus={cancelButtonRef}
onClose={handleClose} onClose={handleClose}
> >
@ -94,7 +95,7 @@ const ConfirmWorkspaceDeletion: React.FC<Props> = ({ isOpen, data, onClose }) =>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}

View File

@ -33,7 +33,7 @@ const ConfirmWorkspaceMemberRemove: React.FC<Props> = ({ isOpen, onClose, data,
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-20"
initialFocus={cancelButtonRef} initialFocus={cancelButtonRef}
onClose={handleClose} onClose={handleClose}
> >
@ -49,7 +49,7 @@ const ConfirmWorkspaceMemberRemove: React.FC<Props> = ({ isOpen, onClose, data,
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}

View File

@ -79,7 +79,7 @@ const SendWorkspaceInvitationModal: React.FC<Props> = ({
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-10" onClose={handleClose}> <Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
@ -92,7 +92,7 @@ const SendWorkspaceInvitationModal: React.FC<Props> = ({
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0"> <div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}

View File

@ -4,6 +4,8 @@ import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import AppLayout from "layouts/app-layout";
// contexts // contexts
@ -32,7 +34,8 @@ import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
// types // types
import { CycleIssueResponse, IIssue, SelectIssue } from "types"; import { CycleIssueResponse, IIssue, SelectIssue, UserAuth } from "types";
import { NextPageContext } from "next";
// fetch-keys // fetch-keys
import { import {
CYCLE_ISSUES, CYCLE_ISSUES,
@ -42,7 +45,7 @@ import {
PROJECT_DETAILS, PROJECT_DETAILS,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
const SingleCycle: React.FC = () => { const SingleCycle: React.FC<UserAuth> = (props) => {
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false); const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>(); const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
@ -109,18 +112,6 @@ const SingleCycle: React.FC = () => {
} }
); );
const partialUpdateIssue = (formData: Partial<IIssue>, issueId: string) => {
if (!workspaceSlug || !projectId) return;
issuesServices
.patchIssue(workspaceSlug as string, projectId as string, issueId, formData)
.then(() => {
mutate(CYCLE_ISSUES(cycleId as string));
})
.catch((error) => {
console.log(error);
});
};
const openCreateIssueModal = ( const openCreateIssueModal = (
issue?: IIssue, issue?: IIssue,
actionType: "create" | "edit" | "delete" = "create" actionType: "create" | "edit" | "delete" = "create"
@ -256,16 +247,16 @@ const SingleCycle: React.FC = () => {
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
removeIssueFromCycle={removeIssueFromCycle} removeIssueFromCycle={removeIssueFromCycle}
setPreloadedData={setPreloadedData} setPreloadedData={setPreloadedData}
userAuth={props}
/> />
<CyclesBoardView <CyclesBoardView
issues={cycleIssuesArray ?? []} issues={cycleIssuesArray ?? []}
removeIssueFromCycle={removeIssueFromCycle}
members={members} members={members}
openCreateIssueModal={openCreateIssueModal} openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
handleDeleteIssue={setDeleteIssue} handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue}
setPreloadedData={setPreloadedData} setPreloadedData={setPreloadedData}
userAuth={props}
/> />
</div> </div>
) : ( ) : (
@ -309,4 +300,32 @@ const SingleCycle: React.FC = () => {
); );
}; };
export const getServerSideProps = async (ctx: NextPageContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.req?.url;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default SingleCycle; export default SingleCycle;

View File

@ -4,7 +4,7 @@ import useSWR, { mutate } from "swr";
import { RectangleStackIcon } from "@heroicons/react/24/outline"; import { RectangleStackIcon } from "@heroicons/react/24/outline";
import { PlusIcon } from "@heroicons/react/20/solid"; import { PlusIcon } from "@heroicons/react/20/solid";
// lib // lib
import { requiredAuth } from "lib/auth"; import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import issuesServices from "services/issues.service"; import issuesServices from "services/issues.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
@ -22,12 +22,12 @@ import View from "components/core/view";
import { Spinner, EmptySpace, EmptySpaceItem, HeaderButton } from "components/ui"; import { Spinner, EmptySpace, EmptySpaceItem, HeaderButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import type { IIssue, IssueResponse } from "types"; import type { IIssue, IssueResponse, UserAuth } from "types";
import type { NextPage, NextPageContext } from "next"; import type { NextPage, NextPageContext } from "next";
// fetch-keys // fetch-keys
import { PROJECT_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys"; import { PROJECT_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
const ProjectIssues: NextPage = () => { const ProjectIssues: NextPage<UserAuth> = (props) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [selectedIssue, setSelectedIssue] = useState< const [selectedIssue, setSelectedIssue] = useState<
(IIssue & { actionType: "edit" | "delete" }) | undefined (IIssue & { actionType: "edit" | "delete" }) | undefined
@ -63,26 +63,6 @@ const ProjectIssues: NextPage = () => {
} }
}, [isOpen]); }, [isOpen]);
const partialUpdateIssue = (formData: Partial<IIssue>, issueId: string) => {
if (!workspaceSlug || !projectId) return;
issuesServices
.patchIssue(workspaceSlug as string, projectId as string, issueId, formData)
.then((response) => {
mutate<IssueResponse>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => ({
...(prevData as IssueResponse),
results:
prevData?.results.map((issue) => (issue.id === response.id ? response : issue)) ?? [],
}),
false
);
})
.catch((error) => {
console.log(error);
});
};
const handleEditIssue = (issue: IIssue) => { const handleEditIssue = (issue: IIssue) => {
setIsOpen(true); setIsOpen(true);
setSelectedIssue({ ...issue, actionType: "edit" }); setSelectedIssue({ ...issue, actionType: "edit" });
@ -134,12 +114,12 @@ const ProjectIssues: NextPage = () => {
<ListView <ListView
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []} issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
handleEditIssue={handleEditIssue} handleEditIssue={handleEditIssue}
partialUpdateIssue={partialUpdateIssue} userAuth={props}
/> />
<BoardView <BoardView
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []} issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
handleDeleteIssue={setDeleteIssue} handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue} userAuth={props}
/> />
</> </>
) : ( ) : (
@ -170,7 +150,6 @@ const ProjectIssues: NextPage = () => {
export const getServerSideProps = async (ctx: NextPageContext) => { export const getServerSideProps = async (ctx: NextPageContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie); const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.req?.url; const redirectAfterSignIn = ctx.req?.url;
if (!user) { if (!user) {
@ -182,9 +161,17 @@ 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 { return {
props: { props: {
user, isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
}, },
}; };
}; };

View File

@ -3,6 +3,9 @@ import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services // services
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
@ -32,7 +35,15 @@ import {
RectangleStackIcon, RectangleStackIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// types // types
import { IIssue, IModule, ModuleIssueResponse, SelectIssue, SelectModuleType } from "types"; import {
IIssue,
IModule,
ModuleIssueResponse,
SelectIssue,
SelectModuleType,
UserAuth,
} from "types";
import { NextPageContext } from "next";
// fetch-keys // fetch-keys
import { import {
MODULE_DETAIL, MODULE_DETAIL,
@ -42,7 +53,7 @@ import {
PROJECT_MEMBERS, PROJECT_MEMBERS,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
const SingleModule = () => { const SingleModule: React.FC<UserAuth> = (props) => {
const [moduleSidebar, setModuleSidebar] = useState(true); const [moduleSidebar, setModuleSidebar] = useState(true);
const [moduleDeleteModal, setModuleDeleteModal] = useState(false); const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>(null); const [selectedIssues, setSelectedIssues] = useState<SelectIssue>(null);
@ -128,18 +139,6 @@ const SingleModule = () => {
} }
}; };
const partialUpdateIssue = (formData: Partial<IIssue>, issueId: string) => {
if (!workspaceSlug || !projectId) return;
issuesService
.patchIssue(workspaceSlug as string, projectId as string, issueId, formData)
.then(() => {
mutate(MODULE_ISSUES(moduleId as string));
})
.catch((error) => {
console.log(error);
});
};
const openCreateIssueModal = ( const openCreateIssueModal = (
issue?: IIssue, issue?: IIssue,
actionType: "create" | "edit" | "delete" = "create" actionType: "create" | "edit" | "delete" = "create"
@ -279,16 +278,16 @@ const SingleModule = () => {
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
removeIssueFromModule={removeIssueFromModule} removeIssueFromModule={removeIssueFromModule}
setPreloadedData={setPreloadedData} setPreloadedData={setPreloadedData}
userAuth={props}
/> />
<ModulesBoardView <ModulesBoardView
issues={moduleIssuesArray ?? []} issues={moduleIssuesArray ?? []}
removeIssueFromModule={removeIssueFromModule}
members={members} members={members}
openCreateIssueModal={openCreateIssueModal} openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
handleDeleteIssue={setDeleteIssue} handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue}
setPreloadedData={setPreloadedData} setPreloadedData={setPreloadedData}
userAuth={props}
/> />
</div> </div>
) : ( ) : (
@ -333,4 +332,32 @@ const SingleModule = () => {
); );
}; };
export const getServerSideProps = async (ctx: NextPageContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.req?.url;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
const projectId = ctx.query.projectId as string;
const workspaceSlug = ctx.query.workspaceSlug as string;
const memberDetail = await requiredAdmin(workspaceSlug, projectId, ctx.req?.headers.cookie);
return {
props: {
isOwner: memberDetail?.role === 20,
isMember: memberDetail?.role === 15,
isViewer: memberDetail?.role === 10,
isGuest: memberDetail?.role === 5,
},
};
};
export default SingleModule; export default SingleModule;

View File

@ -6,7 +6,6 @@ import Image from "next/image";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import type { NextPageContext, NextPage } from "next";
// lib // lib
import { requiredAdmin } from "lib/auth"; import { requiredAdmin } from "lib/auth";
// layouts // layouts
@ -21,6 +20,7 @@ import { Button, CustomSelect, Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import { IProject, IWorkspace } from "types"; import { IProject, IWorkspace } from "types";
import type { NextPageContext, NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS, WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { PROJECTS_LIST, PROJECT_DETAILS, WORKSPACE_MEMBERS } from "constants/fetch-keys";
@ -88,24 +88,9 @@ const ControlSettings: NextPage<TControlSettingsProps> = (props) => {
await projectService await projectService
.updateProject(workspaceSlug as string, projectId as string, payload) .updateProject(workspaceSlug as string, projectId as string, payload)
.then((res) => { .then((res) => {
mutate<IProject>( mutate(PROJECT_DETAILS(projectId as string));
PROJECT_DETAILS(projectId as string), mutate(PROJECTS_LIST(workspaceSlug as string));
(prevData) => ({ ...prevData, ...res }),
false
);
mutate<IProject[]>(
PROJECTS_LIST(workspaceSlug as string),
(prevData) => {
const newData = prevData?.map((item) => {
if (item.id === res.id) {
return res;
}
return item;
});
return newData;
},
false
);
setToastAlert({ setToastAlert({
title: "Success", title: "Success",
type: "success", type: "success",

View File

@ -5,9 +5,6 @@ import useSWR from "swr";
// icons // icons
import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline"; import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline";
// types
import type { NextPage, NextPageContext } from "next";
import type { IWorkspaceMemberInvitation } from "types";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// hooks // hooks
@ -16,9 +13,12 @@ import { requiredAuth } from "lib/auth";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
// components // components
import SingleInvitation from "components/workspace/SingleInvitation"; import SingleInvitation from "components/workspace/single-invitation";
// ui // ui
import { Button, Spinner, EmptySpace, EmptySpaceItem } from "components/ui"; import { Button, Spinner, EmptySpace, EmptySpaceItem } from "components/ui";
// types
import type { NextPage, NextPageContext } from "next";
import type { IWorkspaceMemberInvitation } from "types";
const OnBoard: NextPage = () => { const OnBoard: NextPage = () => {
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]); const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);

View File

@ -34,3 +34,10 @@ export interface IUserLite {
avatar: string; avatar: string;
created_at: Date; created_at: Date;
} }
export type UserAuth = {
isMember: boolean;
isOwner: boolean;
isViewer: boolean;
isGuest: boolean;
};