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 { useRouter } from "next/router";
// swr
import useSWR from "swr";
import useSWR, { mutate } from "swr";
// react-beautiful-dnd
import { DraggableStateSnapshot } from "react-beautiful-dnd";
// headless ui
@ -17,48 +17,49 @@ import issuesService from "services/issues.service";
import stateService from "services/state.service";
import projectService from "services/project.service";
// components
import { AssigneesList } from "components/ui/avatar";
import { CustomSelect, AssigneesList } from "components/ui";
// helpers
import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
import { IIssue, IssueResponse, IUserLite, IWorkspaceMember, Properties } from "types";
import { IIssue, IUserLite, IWorkspaceMember, Properties, UserAuth } from "types";
// common
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";
type Props = {
type?: string;
typeId?: string;
issue: IIssue;
properties: Properties;
snapshot?: DraggableStateSnapshot;
assignees: Partial<IUserLite>[] | (Partial<IUserLite> | undefined)[];
people: IWorkspaceMember[] | undefined;
handleDeleteIssue?: React.Dispatch<React.SetStateAction<string | undefined>>;
partialUpdateIssue: any;
userAuth: UserAuth;
};
const SingleBoardIssue: React.FC<Props> = ({
type,
typeId,
issue,
properties,
snapshot,
assignees,
people,
handleDeleteIssue,
partialUpdateIssue,
userAuth,
}) => {
const router = useRouter();
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(
workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
workspaceSlug && projectId
@ -73,7 +74,25 @@ const SingleBoardIssue: React.FC<Props> = ({
: 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 (
<div
@ -82,7 +101,7 @@ const SingleBoardIssue: React.FC<Props> = ({
}`}
>
<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">
<button
type="button"
@ -114,15 +133,18 @@ const SingleBoardIssue: React.FC<Props> = ({
as="div"
value={issue.priority}
onChange={(data: string) => {
partialUpdateIssue({ priority: data }, issue.id);
partialUpdateIssue({ priority: data });
}}
className="group relative flex-shrink-0"
disabled={isNotAllowed}
>
{({ open }) => (
<>
<div>
<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"
? "bg-red-100 text-red-600"
: issue.priority === "high"
@ -171,14 +193,19 @@ const SingleBoardIssue: React.FC<Props> = ({
as="div"
value={issue.state}
onChange={(data: string) => {
partialUpdateIssue({ state: data }, issue.id);
partialUpdateIssue({ state: data });
}}
className="group relative flex-shrink-0"
disabled={isNotAllowed}
>
{({ open }) => (
<>
<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
className="h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
@ -218,10 +245,6 @@ const SingleBoardIssue: React.FC<Props> = ({
</Listbox.Options>
</Transition>
</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>
@ -242,7 +265,7 @@ const SingleBoardIssue: React.FC<Props> = ({
)}
{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">
{totalChildren} {totalChildren === 1 ? "sub-issue" : "sub-issues"}
{issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
</div>
)}
{properties.assignee && (
@ -255,81 +278,82 @@ const SingleBoardIssue: React.FC<Props> = ({
if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);
else newData.push(data);
partialUpdateIssue({ assignees_list: newData }, issue.id);
partialUpdateIssue({ assignees_list: newData });
}}
className="group relative flex-shrink-0"
disabled={isNotAllowed}
>
{({ open }) => (
<>
<div>
<Listbox.Button>
<div className="flex cursor-pointer items-center gap-1 text-xs">
<AssigneesList users={assignees} length={3} />
</div>
</Listbox.Button>
<Transition
show={open}
as={React.Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
<div>
<Listbox.Button>
<div
className={`flex ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
} items-center gap-1 text-xs`}
>
<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}
<AssigneesList users={assignees} length={3} />
</div>
</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">
{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
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"
}`}
>
{person.member.avatar && person.member.avatar !== "" ? (
<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.avatar && person.member.avatar !== "" ? (
<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
: person.member.email}
</p>
</div>
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
? 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.email}
</p>
</div>
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
)}
</Listbox>
)}

View File

@ -21,7 +21,7 @@ import { CalendarDaysIcon } from "@heroicons/react/24/outline";
import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
import { IIssue, IWorkspaceMember, Properties } from "types";
import { IIssue, IWorkspaceMember, Properties, UserAuth } from "types";
// fetch-keys
import {
CYCLE_ISSUES,
@ -41,6 +41,7 @@ type Props = {
properties: Properties;
editIssue: () => void;
removeIssue?: () => void;
userAuth: UserAuth;
};
const SingleListIssue: React.FC<Props> = ({
@ -50,6 +51,7 @@ const SingleListIssue: React.FC<Props> = ({
properties,
editIssue,
removeIssue,
userAuth,
}) => {
const [deleteIssue, setDeleteIssue] = useState<IIssue | undefined>();
@ -86,6 +88,8 @@ const SingleListIssue: React.FC<Props> = ({
});
};
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
return (
<>
<ConfirmIssueDeletion
@ -121,12 +125,15 @@ const SingleListIssue: React.FC<Props> = ({
partialUpdateIssue({ priority: data });
}}
className="group relative flex-shrink-0"
disabled={isNotAllowed}
>
{({ open }) => (
<>
<div>
<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"
? "bg-red-100 text-red-600"
: issue.priority === "high"
@ -210,6 +217,7 @@ const SingleListIssue: React.FC<Props> = ({
}}
maxHeight="md"
noChevron
disabled={isNotAllowed}
>
{states?.map((state) => (
<CustomSelect.Option key={state.id} value={state.id}>
@ -270,12 +278,17 @@ const SingleListIssue: React.FC<Props> = ({
partialUpdateIssue({ assignees_list: newData });
}}
className="group relative flex-shrink-0"
disabled={isNotAllowed}
>
{({ open }) => (
<>
<div>
<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 ?? []} />
</div>
</Listbox.Button>
@ -325,7 +338,7 @@ const SingleListIssue: React.FC<Props> = ({
)}
</Listbox>
)}
{type && (
{type && !isNotAllowed && (
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={editIssue}>Edit</CustomMenu.MenuItem>
{type !== "issue" && (

View File

@ -2,19 +2,19 @@ import React, { useEffect, useRef, useState } from "react";
import { mutate } from "swr";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// services
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import type { IProject, IWorkspace } from "types";
import projectService from "services/project.service";
// hooks
import useToast from "hooks/use-toast";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { Button, Input } from "components/ui";
// types
// constants
import type { IProject, IWorkspace } from "types";
// fetch-keys
import { PROJECTS_LIST } from "constants/fetch-keys";
type TConfirmProjectDeletionProps = {
@ -86,7 +86,7 @@ const ConfirmProjectDeletion: React.FC<TConfirmProjectDeletionProps> = (props) =
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog
as="div"
className="relative z-10"
className="relative z-20"
initialFocus={cancelButtonRef}
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" />
</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">
<Transition.Child
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}>
<Dialog
as="div"
className="relative z-10"
className="relative z-20"
initialFocus={cancelButtonRef}
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" />
</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">
<Transition.Child
as={React.Fragment}

View File

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

View File

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

View File

@ -7,14 +7,14 @@ import { mutate } from "swr";
import { Controller, useForm } from "react-hook-form";
// headless
import { Dialog, Transition } from "@headlessui/react";
// types
import type { ICycle } from "types";
// services
import cycleService from "services/cycles.service";
import { Button, Input, TextArea, CustomSelect } from "components/ui";
// ui
import { Button, Input, TextArea, CustomSelect } from "components/ui";
// common
import { renderDateFormat } from "helpers/date-time.helper";
// types
import type { ICycle } from "types";
// 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),
(prevData) => {
const newData = prevData?.map((item) => {
if (item.id === res.id) {
return res;
}
if (item.id === res.id) return res;
return item;
});
return newData;
},
false

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import SingleListIssue from "components/common/list-view/single-issue";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
import { IIssue, IWorkspaceMember } from "types";
import { IIssue, IWorkspaceMember, UserAuth } from "types";
// 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 = {
issues: IIssue[];
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 [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
@ -130,6 +130,7 @@ const ListView: React.FC<Props> = ({ issues, handleEditIssue }) => {
issue={issue}
properties={properties}
editIssue={() => handleEditIssue(issue)}
userAuth={userAuth}
/>
);
})

View File

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

View File

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

View File

@ -18,7 +18,7 @@ import { CustomMenu, Spinner } from "components/ui";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
import { IIssue, IWorkspaceMember } from "types";
import { IIssue, IWorkspaceMember, UserAuth } from "types";
// fetch-keys
import { STATE_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
@ -35,6 +35,7 @@ type Props = {
| null
>
>;
userAuth: UserAuth;
};
const ModulesListView: React.FC<Props> = ({
@ -43,6 +44,7 @@ const ModulesListView: React.FC<Props> = ({
openIssuesListModal,
removeIssueFromModule,
setPreloadedData,
userAuth,
}) => {
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
@ -137,6 +139,7 @@ const ModulesListView: React.FC<Props> = ({
properties={properties}
editIssue={() => openCreateIssueModal(issue, "edit")}
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";
// ui
import { ChevronDownIcon, CheckIcon } from "@heroicons/react/20/solid";
import { Button, Select, TextArea } from "components/ui";
import { Button, CustomSelect, Select, TextArea } from "components/ui";
// hooks
import useToast from "hooks/use-toast";
// services
@ -106,7 +106,7 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
return (
<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
as={React.Fragment}
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" />
</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">
<Transition.Child
as={React.Fragment}
@ -196,40 +196,17 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
uninvitedPeople?.map((person) => (
<Listbox.Option
key={person.member.id}
className={({ active }) =>
`${
active ? "bg-theme text-white" : "text-gray-900"
} relative cursor-default select-none py-2 pl-3 pr-9 text-left`
className={({ active, selected }) =>
`${active ? "bg-indigo-50" : ""} ${
selected ? "bg-indigo-50 font-medium" : ""
} text-gray-900 cursor-default select-none p-2`
}
value={{
id: person.member.id,
email: person.member.email,
}}
>
{({ selected, active }) => (
<>
<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}
</>
)}
{person.member.email}
</Listbox.Option>
))
)}
@ -246,22 +223,28 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
/>
</div>
<div>
<div>
<Select
id="role"
label="Role"
name="role"
error={errors.role}
register={register}
validations={{
required: "Role is required",
}}
options={Object.entries(ROLE).map(([key, value]) => ({
value: key,
label: value,
}))}
/>
</div>
<h6 className="text-gray-500">Role</h6>
<Controller
name="role"
control={control}
render={({ field }) => (
<CustomSelect
{...field}
label={
<span className="capitalize">
{field.value ? ROLE[field.value] : "Select role"}
</span>
}
input
>
{Object.entries(ROLE).map(([key, label]) => (
<CustomSelect.Option key={key} value={key}>
{label}
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div>
<div>
<TextArea

View File

@ -14,6 +14,7 @@ type CustomSelectProps = {
width?: "auto" | string;
input?: boolean;
noChevron?: boolean;
disabled?: boolean;
};
const CustomSelect = ({
@ -26,11 +27,20 @@ const CustomSelect = ({
width = "auto",
input = false,
noChevron = false,
disabled = false,
}: 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>
<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"
} ${
textAlignment === "right"

View File

@ -4,19 +4,20 @@ import { useRouter } from "next/router";
import { mutate } from "swr";
// headless ui
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
import workspaceService from "services/workspace.service";
// hooks
import useToast from "hooks/use-toast";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { Button, Input } from "components/ui";
// types
import type { IWorkspace } from "types";
// fetch-keys
import { USER_WORKSPACES } from "constants/fetch-keys";
type Props = {
isOpen: boolean;
@ -78,7 +79,7 @@ const ConfirmWorkspaceDeletion: React.FC<Props> = ({ isOpen, data, onClose }) =>
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog
as="div"
className="relative z-10"
className="relative z-20"
initialFocus={cancelButtonRef}
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" />
</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">
<Transition.Child
as={React.Fragment}

View File

@ -33,7 +33,7 @@ const ConfirmWorkspaceMemberRemove: React.FC<Props> = ({ isOpen, onClose, data,
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog
as="div"
className="relative z-10"
className="relative z-20"
initialFocus={cancelButtonRef}
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" />
</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">
<Transition.Child
as={React.Fragment}

View File

@ -79,7 +79,7 @@ const SendWorkspaceInvitationModal: React.FC<Props> = ({
return (
<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
as={React.Fragment}
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" />
</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">
<Transition.Child
as={React.Fragment}

View File

@ -4,6 +4,8 @@ import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// layouts
import AppLayout from "layouts/app-layout";
// contexts
@ -32,7 +34,8 @@ import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
// types
import { CycleIssueResponse, IIssue, SelectIssue } from "types";
import { CycleIssueResponse, IIssue, SelectIssue, UserAuth } from "types";
import { NextPageContext } from "next";
// fetch-keys
import {
CYCLE_ISSUES,
@ -42,7 +45,7 @@ import {
PROJECT_DETAILS,
} from "constants/fetch-keys";
const SingleCycle: React.FC = () => {
const SingleCycle: React.FC<UserAuth> = (props) => {
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
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 = (
issue?: IIssue,
actionType: "create" | "edit" | "delete" = "create"
@ -256,16 +247,16 @@ const SingleCycle: React.FC = () => {
openIssuesListModal={openIssuesListModal}
removeIssueFromCycle={removeIssueFromCycle}
setPreloadedData={setPreloadedData}
userAuth={props}
/>
<CyclesBoardView
issues={cycleIssuesArray ?? []}
removeIssueFromCycle={removeIssueFromCycle}
members={members}
openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal}
handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue}
setPreloadedData={setPreloadedData}
userAuth={props}
/>
</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;

View File

@ -4,7 +4,7 @@ import useSWR, { mutate } from "swr";
import { RectangleStackIcon } from "@heroicons/react/24/outline";
import { PlusIcon } from "@heroicons/react/20/solid";
// lib
import { requiredAuth } from "lib/auth";
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import issuesServices from "services/issues.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 { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types
import type { IIssue, IssueResponse } from "types";
import type { IIssue, IssueResponse, UserAuth } from "types";
import type { NextPage, NextPageContext } from "next";
// 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 [selectedIssue, setSelectedIssue] = useState<
(IIssue & { actionType: "edit" | "delete" }) | undefined
@ -63,26 +63,6 @@ const ProjectIssues: NextPage = () => {
}
}, [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) => {
setIsOpen(true);
setSelectedIssue({ ...issue, actionType: "edit" });
@ -134,12 +114,12 @@ const ProjectIssues: NextPage = () => {
<ListView
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
handleEditIssue={handleEditIssue}
partialUpdateIssue={partialUpdateIssue}
userAuth={props}
/>
<BoardView
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue}
userAuth={props}
/>
</>
) : (
@ -170,7 +150,6 @@ const ProjectIssues: NextPage = () => {
export const getServerSideProps = async (ctx: NextPageContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.req?.url;
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 {
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 useSWR, { mutate } from "swr";
// lib
import { requiredAdmin, requiredAuth } from "lib/auth";
// services
import modulesService from "services/modules.service";
import projectService from "services/project.service";
@ -32,7 +35,15 @@ import {
RectangleStackIcon,
} from "@heroicons/react/24/outline";
// types
import { IIssue, IModule, ModuleIssueResponse, SelectIssue, SelectModuleType } from "types";
import {
IIssue,
IModule,
ModuleIssueResponse,
SelectIssue,
SelectModuleType,
UserAuth,
} from "types";
import { NextPageContext } from "next";
// fetch-keys
import {
MODULE_DETAIL,
@ -42,7 +53,7 @@ import {
PROJECT_MEMBERS,
} from "constants/fetch-keys";
const SingleModule = () => {
const SingleModule: React.FC<UserAuth> = (props) => {
const [moduleSidebar, setModuleSidebar] = useState(true);
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
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 = (
issue?: IIssue,
actionType: "create" | "edit" | "delete" = "create"
@ -279,16 +278,16 @@ const SingleModule = () => {
openIssuesListModal={openIssuesListModal}
removeIssueFromModule={removeIssueFromModule}
setPreloadedData={setPreloadedData}
userAuth={props}
/>
<ModulesBoardView
issues={moduleIssuesArray ?? []}
removeIssueFromModule={removeIssueFromModule}
members={members}
openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal}
handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue}
setPreloadedData={setPreloadedData}
userAuth={props}
/>
</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;

View File

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

View File

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

View File

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