forked from github/plane
Merge pull request #28 from dakshesh14/main
fix: mutation in project, issues page, feat: made redirection UX better
This commit is contained in:
commit
1368fb9164
@ -1,14 +1,15 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
// swr
|
// swr
|
||||||
import { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
// react hook form
|
// react hook form
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// headless
|
// headless
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import projectServices from "lib/services/project.service";
|
import projectServices from "lib/services/project.service";
|
||||||
|
import workspaceService from "lib/services/workspace.service";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
import { PROJECTS_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
import useToast from "lib/hooks/useToast";
|
import useToast from "lib/hooks/useToast";
|
||||||
@ -17,7 +18,7 @@ import { Button, Input, TextArea, Select } from "ui";
|
|||||||
// common
|
// common
|
||||||
import { debounce } from "constants/common";
|
import { debounce } from "constants/common";
|
||||||
// types
|
// types
|
||||||
import { IProject } from "types";
|
import { IProject, WorkspaceMember } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -31,6 +32,23 @@ const defaultValues: Partial<IProject> = {
|
|||||||
description: "",
|
description: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const IsGuestCondition: React.FC<{
|
||||||
|
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}> = ({ setIsOpen }) => {
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
setToastAlert({
|
||||||
|
title: "Error",
|
||||||
|
type: "error",
|
||||||
|
message: "You don't have permission to create project.",
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
@ -40,7 +58,12 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { activeWorkspace } = useUser();
|
const { activeWorkspace, user } = useUser();
|
||||||
|
|
||||||
|
const { data: workspaceMembers } = useSWR<WorkspaceMember[]>(
|
||||||
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
||||||
|
);
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -56,6 +79,8 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
setValue,
|
setValue,
|
||||||
} = useForm<IProject>({
|
} = useForm<IProject>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
|
reValidateMode: "onChange",
|
||||||
|
mode: "all",
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (formData: IProject) => {
|
const onSubmit = async (formData: IProject) => {
|
||||||
@ -77,6 +102,15 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
if (err.status === 403) {
|
||||||
|
setToastAlert({
|
||||||
|
title: "Error",
|
||||||
|
type: "error",
|
||||||
|
message: "You don't have permission to create project.",
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
Object.keys(err).map((key) => {
|
Object.keys(err).map((key) => {
|
||||||
const errorMessages = err[key];
|
const errorMessages = err[key];
|
||||||
setError(key as keyof IProject, {
|
setError(key as keyof IProject, {
|
||||||
@ -105,6 +139,15 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
}
|
}
|
||||||
}, [projectName, projectIdentifier, setValue, isChangeIdentifierRequired]);
|
}, [projectName, projectIdentifier, setValue, isChangeIdentifierRequired]);
|
||||||
|
|
||||||
|
if (workspaceMembers) {
|
||||||
|
const isMember = workspaceMembers.find((member) => member.member.id === user?.id);
|
||||||
|
const isGuest = workspaceMembers.find(
|
||||||
|
(member) => member.member.id === user?.id && member.role === 5
|
||||||
|
);
|
||||||
|
|
||||||
|
if ((!isMember || isGuest) && isOpen) return <IsGuestCondition setIsOpen={setIsOpen} />;
|
||||||
|
}
|
||||||
|
|
||||||
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-10" onClose={handleClose}>
|
||||||
@ -203,8 +246,8 @@ const CreateProjectModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
|
|||||||
message: "Identifier must at least be of 1 character",
|
message: "Identifier must at least be of 1 character",
|
||||||
},
|
},
|
||||||
maxLength: {
|
maxLength: {
|
||||||
value: 9,
|
value: 5,
|
||||||
message: "Identifier must at most be of 9 characters",
|
message: "Identifier must at most be of 5 characters",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -50,8 +50,14 @@ const SendProjectInvitationModal: React.FC<Props> = ({ isOpen, setIsOpen, member
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR<WorkspaceMember[]>(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null,
|
||||||
|
{
|
||||||
|
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||||
|
if (err?.status === 403) return;
|
||||||
|
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -42,8 +42,8 @@ type Props = {
|
|||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
bgColor?: string;
|
bgColor?: string;
|
||||||
stateId?: string;
|
stateId: string | null;
|
||||||
createdBy?: string;
|
createdBy: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SingleBoard: React.FC<Props> = ({
|
const SingleBoard: React.FC<Props> = ({
|
||||||
@ -109,7 +109,7 @@ const SingleBoard: React.FC<Props> = ({
|
|||||||
<span
|
<span
|
||||||
className={`w-3 h-3 block rounded-full ${!show ? "" : "mr-1"}`}
|
className={`w-3 h-3 block rounded-full ${!show ? "" : "mr-1"}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: bgColor,
|
backgroundColor: Boolean(bgColor) ? bgColor : undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<h2
|
<h2
|
||||||
@ -151,7 +151,7 @@ const SingleBoard: React.FC<Props> = ({
|
|||||||
setIsIssueOpen(true);
|
setIsIssueOpen(true);
|
||||||
if (selectedGroup !== null)
|
if (selectedGroup !== null)
|
||||||
setPreloadedData({
|
setPreloadedData({
|
||||||
state: stateId,
|
state: stateId !== null ? stateId : undefined,
|
||||||
[selectedGroup]: groupTitle,
|
[selectedGroup]: groupTitle,
|
||||||
actionType: "createIssue",
|
actionType: "createIssue",
|
||||||
});
|
});
|
||||||
@ -201,10 +201,12 @@ const SingleBoard: React.FC<Props> = ({
|
|||||||
? "text-xs text-black"
|
? "text-xs text-black"
|
||||||
: key === "priority"
|
: key === "priority"
|
||||||
? `text-xs bg-gray-200 px-2 py-1 mt-2 flex items-center gap-x-1 rounded w-min whitespace-nowrap capitalize font-medium ${
|
? `text-xs bg-gray-200 px-2 py-1 mt-2 flex items-center gap-x-1 rounded w-min whitespace-nowrap capitalize font-medium ${
|
||||||
childIssue.priority === "high"
|
childIssue.priority === "urgent"
|
||||||
? "bg-red-100 text-red-600"
|
? "bg-red-100 text-red-600"
|
||||||
|
: childIssue.priority === "high"
|
||||||
|
? "bg-orange-100 text-orange-600"
|
||||||
: childIssue.priority === "medium"
|
: childIssue.priority === "medium"
|
||||||
? "bg-orange-100 text-orange-500"
|
? "bg-yellow-100 text-yellow-500"
|
||||||
: childIssue.priority === "low"
|
: childIssue.priority === "low"
|
||||||
? "bg-green-100 text-green-500"
|
? "bg-green-100 text-green-500"
|
||||||
: "hidden"
|
: "hidden"
|
||||||
@ -224,7 +226,7 @@ const SingleBoard: React.FC<Props> = ({
|
|||||||
: "None"}
|
: "None"}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{key === "target_date" && (
|
{key === "due_date" && (
|
||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
className={`flex items-center gap-x-1 group ${
|
className={`flex items-center gap-x-1 group ${
|
||||||
@ -320,7 +322,7 @@ const SingleBoard: React.FC<Props> = ({
|
|||||||
setIsIssueOpen(true);
|
setIsIssueOpen(true);
|
||||||
if (selectedGroup !== null) {
|
if (selectedGroup !== null) {
|
||||||
setPreloadedData({
|
setPreloadedData({
|
||||||
state: stateId,
|
state: stateId !== null ? stateId : undefined,
|
||||||
[selectedGroup]: groupTitle,
|
[selectedGroup]: groupTitle,
|
||||||
actionType: "createIssue",
|
actionType: "createIssue",
|
||||||
});
|
});
|
||||||
|
@ -197,9 +197,10 @@ const BoardView: React.FC<Props> = ({ properties, selectedGroup, groupedByIssues
|
|||||||
selectedGroup={selectedGroup}
|
selectedGroup={selectedGroup}
|
||||||
groupTitle={singleGroup}
|
groupTitle={singleGroup}
|
||||||
createdBy={
|
createdBy={
|
||||||
members
|
selectedGroup === "created_by"
|
||||||
? members?.find((m) => m.member.id === singleGroup)?.member.first_name
|
? members?.find((m) => m.member.id === singleGroup)?.member
|
||||||
: undefined
|
.first_name ?? "loading..."
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
groupedByIssues={groupedByIssues}
|
groupedByIssues={groupedByIssues}
|
||||||
index={index}
|
index={index}
|
||||||
@ -208,8 +209,8 @@ const BoardView: React.FC<Props> = ({ properties, selectedGroup, groupedByIssues
|
|||||||
setPreloadedData={setPreloadedData}
|
setPreloadedData={setPreloadedData}
|
||||||
stateId={
|
stateId={
|
||||||
selectedGroup === "state_detail.name"
|
selectedGroup === "state_detail.name"
|
||||||
? states?.find((s) => s.name === singleGroup)?.id
|
? states?.find((s) => s.name === singleGroup)?.id ?? null
|
||||||
: undefined
|
: null
|
||||||
}
|
}
|
||||||
bgColor={
|
bgColor={
|
||||||
selectedGroup === "state_detail.name"
|
selectedGroup === "state_detail.name"
|
||||||
|
@ -384,7 +384,7 @@ const ListView: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</td>
|
</td>
|
||||||
) : (key as keyof Properties) === "target_date" ? (
|
) : (key as keyof Properties) === "due_date" ? (
|
||||||
<td className="px-3 py-4 text-sm font-medium text-gray-900 whitespace-nowrap">
|
<td className="px-3 py-4 text-sm font-medium text-gray-900 whitespace-nowrap">
|
||||||
{issue.target_date
|
{issue.target_date
|
||||||
? renderShortNumericDateFormat(issue.target_date)
|
? renderShortNumericDateFormat(issue.target_date)
|
||||||
@ -440,4 +440,4 @@ const ListView: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ListView;
|
export default ListView;
|
||||||
|
@ -68,7 +68,7 @@ const IssueDetailSidebar: React.FC<Props> = ({ control, submitChanges, issueDeta
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR<WorkspaceMember[]>(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,10 +3,14 @@ import React, { useState } from "react";
|
|||||||
// next
|
// next
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import _ from "lodash";
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// Services
|
// Services
|
||||||
import projectService from "lib/services/project.service";
|
import projectService from "lib/services/project.service";
|
||||||
|
// fetch keys
|
||||||
|
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
// commons
|
||||||
|
import { renderShortNumericDateFormat } from "constants/common";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import {
|
||||||
CalendarDaysIcon,
|
CalendarDaysIcon,
|
||||||
@ -17,25 +21,41 @@ import {
|
|||||||
PlusIcon,
|
PlusIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { renderShortNumericDateFormat } from "constants/common";
|
// types
|
||||||
|
import type { IProject } from "types";
|
||||||
|
type Props = {
|
||||||
|
project: IProject;
|
||||||
|
slug: string;
|
||||||
|
invitationsRespond: string[];
|
||||||
|
handleInvitation: (project_invitation: any, action: "accepted" | "withdraw") => void;
|
||||||
|
setDeleteProject: React.Dispatch<React.SetStateAction<IProject | undefined>>;
|
||||||
|
};
|
||||||
|
|
||||||
const ProjectMemberInvitations = ({
|
const ProjectMemberInvitations: React.FC<Props> = ({
|
||||||
project,
|
project,
|
||||||
slug,
|
slug,
|
||||||
invitationsRespond,
|
invitationsRespond,
|
||||||
handleInvitation,
|
handleInvitation,
|
||||||
setDeleteProject,
|
setDeleteProject,
|
||||||
}: any) => {
|
}) => {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { data: members } = useSWR("PROJECT_MEMBERS", () =>
|
|
||||||
|
const { data: members } = useSWR<any[]>(PROJECT_MEMBERS(project.id), () =>
|
||||||
projectService.projectMembers(slug, project.id)
|
projectService.projectMembers(slug, project.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const isMember =
|
const isMember = members?.some((item: any) => item.member.id === (user as any)?.id);
|
||||||
_.filter(members, (item: any) => item.member.id === (user as any).id).length === 1;
|
|
||||||
|
|
||||||
const [selected, setSelected] = useState<any>(false);
|
const [selected, setSelected] = useState<any>(false);
|
||||||
|
|
||||||
|
if (!members) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-36 flex flex-col px-4 py-3 rounded-md bg-white">
|
||||||
|
<div className="w-full h-full bg-gray-50 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
@ -2,7 +2,7 @@ export const CURRENT_USER = "CURRENT_USER";
|
|||||||
export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS";
|
export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS";
|
||||||
export const USER_WORKSPACES = "USER_WORKSPACES";
|
export const USER_WORKSPACES = "USER_WORKSPACES";
|
||||||
|
|
||||||
export const WORKSPACE_MEMBERS = "WORKSPACE_MEMBERS";
|
export const WORKSPACE_MEMBERS = (workspaceSlug: string) => `WORKSPACE_MEMBERS_${workspaceSlug}`;
|
||||||
export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS";
|
export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS";
|
||||||
export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION";
|
export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION";
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import authenticationService from "lib/services/authentication.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
import useTheme from "lib/hooks/useTheme";
|
import useTheme from "lib/hooks/useTheme";
|
||||||
|
import useToast from "lib/hooks/useToast";
|
||||||
// components
|
// components
|
||||||
import CreateProjectModal from "components/project/CreateProjectModal";
|
import CreateProjectModal from "components/project/CreateProjectModal";
|
||||||
// headless ui
|
// headless ui
|
||||||
@ -119,6 +120,8 @@ const Sidebar: React.FC = () => {
|
|||||||
|
|
||||||
const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme();
|
const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme();
|
||||||
|
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="h-full">
|
<nav className="h-full">
|
||||||
<CreateProjectModal isOpen={isCreateProjectModal} setIsOpen={setCreateProjectModal} />
|
<CreateProjectModal isOpen={isCreateProjectModal} setIsOpen={setCreateProjectModal} />
|
||||||
@ -282,9 +285,11 @@ const Sidebar: React.FC = () => {
|
|||||||
last_workspace_id: workspace?.id,
|
last_workspace_id: workspace?.id,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
router.push("/workspace");
|
const isInProject =
|
||||||
|
router.pathname.includes("/[projectId]/");
|
||||||
|
if (isInProject) router.push("/workspace");
|
||||||
})
|
})
|
||||||
.catch((err) => console.log);
|
.catch((err) => console.error(err));
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
active ? "bg-theme text-white" : "text-gray-900"
|
active ? "bg-theme text-white" : "text-gray-900"
|
||||||
@ -478,7 +483,13 @@ const Sidebar: React.FC = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
copyTextToClipboard(
|
copyTextToClipboard(
|
||||||
`https://app.plane.so/projects/${project?.id}/issues/`
|
`https://app.plane.so/projects/${project?.id}/issues/`
|
||||||
)
|
).then(() => {
|
||||||
|
setToastAlert({
|
||||||
|
title: "Link Copied",
|
||||||
|
message: "Link copied to clipboard",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ClipboardDocumentIcon className="h-3 w-3" />
|
<ClipboardDocumentIcon className="h-3 w-3" />
|
||||||
|
@ -4,7 +4,7 @@ import type { NextPage } from "next";
|
|||||||
// redirect
|
// redirect
|
||||||
import redirect from "lib/redirect";
|
import redirect from "lib/redirect";
|
||||||
|
|
||||||
const withAuth = (WrappedComponent: NextPage) => {
|
const withAuth = (WrappedComponent: NextPage, getBackToSameRoute: boolean = true) => {
|
||||||
const Wrapper: NextPage<any> = (props) => {
|
const Wrapper: NextPage<any> = (props) => {
|
||||||
return <WrappedComponent {...props} />;
|
return <WrappedComponent {...props} />;
|
||||||
};
|
};
|
||||||
@ -17,7 +17,8 @@ const withAuth = (WrappedComponent: NextPage) => {
|
|||||||
const token = cookies?.split("accessToken=")?.[1]?.split(";")?.[0];
|
const token = cookies?.split("accessToken=")?.[1]?.split(";")?.[0];
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
redirect(ctx, "/signin");
|
if (getBackToSameRoute) redirect(ctx, "/signin?next=" + ctx?.asPath);
|
||||||
|
else redirect(ctx, "/signin");
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageProps =
|
const pageProps =
|
||||||
|
@ -78,6 +78,10 @@ const useIssuesFilter = (projectIssues?: IssueResponse) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (groupByProperty === "priority" && orderBy === "priority") {
|
||||||
|
setOrderBy(null);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupedByIssues,
|
groupedByIssues,
|
||||||
issueView,
|
issueView,
|
||||||
|
@ -16,7 +16,7 @@ const initialValues: Properties = {
|
|||||||
assignee: true,
|
assignee: true,
|
||||||
priority: false,
|
priority: false,
|
||||||
start_date: false,
|
start_date: false,
|
||||||
target_date: false,
|
due_date: false,
|
||||||
cycle: false,
|
cycle: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class ProjectServices extends APIService {
|
|||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +87,18 @@ const ProjectIssues: NextPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: members } = useSWR<ProjectMember[]>(
|
const { data: members } = useSWR<ProjectMember[]>(
|
||||||
activeWorkspace && activeProject ? PROJECT_MEMBERS : null,
|
activeWorkspace && activeProject
|
||||||
|
? PROJECT_MEMBERS(activeWorkspace.slug, activeProject.id)
|
||||||
|
: null,
|
||||||
activeWorkspace && activeProject
|
activeWorkspace && activeProject
|
||||||
? () => projectService.projectMembers(activeWorkspace.slug, activeProject.id)
|
? () => projectService.projectMembers(activeWorkspace.slug, activeProject.id)
|
||||||
: null
|
: null,
|
||||||
|
{
|
||||||
|
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||||
|
if (err?.status === 403) return;
|
||||||
|
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -173,7 +181,7 @@ const ProjectIssues: NextPage = () => {
|
|||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
open ? "text-gray-900" : "text-gray-500",
|
open ? "text-gray-900" : "text-gray-500",
|
||||||
"group inline-flex items-center rounded-md bg-transparent text-base font-medium hover:text-gray-900 focus:outline-none border border-gray-300 px-3 py-1"
|
"group inline-flex items-center rounded-md bg-transparent text-xs font-medium hover:text-gray-900 focus:outline-none border border-gray-300 px-2 py-2"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>View</span>
|
<span>View</span>
|
||||||
@ -195,7 +203,7 @@ const ProjectIssues: NextPage = () => {
|
|||||||
leaveFrom="opacity-100 translate-y-0"
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute mr-5 right-1/2 z-10 mt-3 w-screen max-w-xs translate-x-1/2 transform px-2 sm:px-0 bg-gray-0 backdrop-filter backdrop-blur-xl bg-opacity-100 rounded-lg shadow-lg overflow-hidden">
|
<Popover.Panel className="absolute mr-5 right-1/2 z-10 mt-3 w-screen max-w-xs translate-x-1/2 transform px-2 sm:px-0 bg-white rounded-lg shadow-lg overflow-hidden">
|
||||||
<div className="overflow-hidden py-8 px-4">
|
<div className="overflow-hidden py-8 px-4">
|
||||||
<div className="relative flex flex-col gap-1 gap-y-4">
|
<div className="relative flex flex-col gap-1 gap-y-4">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
|
@ -53,7 +53,13 @@ const ProjectMembers: NextPage = () => {
|
|||||||
activeWorkspace && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
activeWorkspace && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||||
activeWorkspace && projectId
|
activeWorkspace && projectId
|
||||||
? () => projectService.projectMembers(activeWorkspace.slug, projectId as any)
|
? () => projectService.projectMembers(activeWorkspace.slug, projectId as any)
|
||||||
: null
|
: null,
|
||||||
|
{
|
||||||
|
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||||
|
if (err?.status === 403) return;
|
||||||
|
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const { data: projectInvitations, mutate: mutateInvitations } = useSWR(
|
const { data: projectInvitations, mutate: mutateInvitations } = useSWR(
|
||||||
activeWorkspace && projectId ? PROJECT_INVITATIONS : null,
|
activeWorkspace && projectId ? PROJECT_INVITATIONS : null,
|
||||||
@ -235,7 +241,7 @@ const ProjectMembers: NextPage = () => {
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 sm:pl-6">
|
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 sm:pl-6">
|
||||||
{member.status ? (
|
{member.member ? (
|
||||||
<span className="p-0.5 px-2 text-sm bg-green-700 text-white rounded-full">
|
<span className="p-0.5 px-2 text-sm bg-green-700 text-white rounded-full">
|
||||||
Active
|
Active
|
||||||
</span>
|
</span>
|
||||||
@ -261,7 +267,7 @@ const ProjectMembers: NextPage = () => {
|
|||||||
className="w-full text-left py-2 pl-2"
|
className="w-full text-left py-2 pl-2"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!member.status) {
|
if (!member.member) {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
message: "You can't edit a pending invitation.",
|
message: "You can't edit a pending invitation.",
|
||||||
@ -282,7 +288,7 @@ const ProjectMembers: NextPage = () => {
|
|||||||
className="w-full text-left py-2 pl-2"
|
className="w-full text-left py-2 pl-2"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (member.status) {
|
if (member.member) {
|
||||||
setSelectedRemoveMember(member.id);
|
setSelectedRemoveMember(member.id);
|
||||||
} else {
|
} else {
|
||||||
setSelectedInviteRemoveMember(member.id);
|
setSelectedInviteRemoveMember(member.id);
|
||||||
|
@ -78,8 +78,14 @@ const ProjectSettings: NextPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: people } = useSWR<WorkspaceMember[]>(
|
const { data: people } = useSWR<WorkspaceMember[]>(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null,
|
||||||
|
{
|
||||||
|
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||||
|
if (err?.status === 403) return;
|
||||||
|
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -541,4 +547,4 @@ const ProjectSettings: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectSettings;
|
export default ProjectSettings;
|
||||||
|
@ -43,8 +43,14 @@ const SignIn: NextPage = () => {
|
|||||||
async (res: any) => {
|
async (res: any) => {
|
||||||
await mutateUser();
|
await mutateUser();
|
||||||
await mutateWorkspaces();
|
await mutateWorkspaces();
|
||||||
if (res.user.is_onboarded) router.push("/");
|
const nextLocation = router.asPath.split("?next=")[1];
|
||||||
else router.push("/invitations");
|
|
||||||
|
if (nextLocation) {
|
||||||
|
router.push(nextLocation as string);
|
||||||
|
} else {
|
||||||
|
if (res.user.is_onboarded) router.push("/");
|
||||||
|
else router.push("/invitations");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[mutateUser, mutateWorkspaces, router]
|
[mutateUser, mutateWorkspaces, router]
|
||||||
);
|
);
|
||||||
|
@ -41,7 +41,7 @@ const WorkspaceInvite: NextPage = () => {
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: workspaceMembers, mutate: mutateMembers } = useSWR<any[]>(
|
const { data: workspaceMembers, mutate: mutateMembers } = useSWR<any[]>(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
activeWorkspace ? WORKSPACE_MEMBERS(activeWorkspace.slug) : null,
|
||||||
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null
|
||||||
);
|
);
|
||||||
const { data: workspaceInvitations, mutate: mutateInvitations } = useSWR<any[]>(
|
const { data: workspaceInvitations, mutate: mutateInvitations } = useSWR<any[]>(
|
||||||
@ -229,7 +229,7 @@ const WorkspaceInvite: NextPage = () => {
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 sm:pl-6">
|
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 sm:pl-6">
|
||||||
{member.status ? (
|
{member.member ? (
|
||||||
<span className="p-0.5 px-2 text-sm bg-green-700 text-white rounded-full">
|
<span className="p-0.5 px-2 text-sm bg-green-700 text-white rounded-full">
|
||||||
Active
|
Active
|
||||||
</span>
|
</span>
|
||||||
@ -277,7 +277,7 @@ const WorkspaceInvite: NextPage = () => {
|
|||||||
className="w-full text-left py-2 pl-2"
|
className="w-full text-left py-2 pl-2"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (member.status) {
|
if (member.member) {
|
||||||
setSelectedRemoveMember(member.id);
|
setSelectedRemoveMember(member.id);
|
||||||
} else {
|
} else {
|
||||||
setSelectedInviteRemoveMember(member.id);
|
setSelectedInviteRemoveMember(member.id);
|
||||||
|
2
apps/app/types/issues.d.ts
vendored
2
apps/app/types/issues.d.ts
vendored
@ -102,7 +102,7 @@ export type Properties = {
|
|||||||
assignee: boolean;
|
assignee: boolean;
|
||||||
priority: boolean;
|
priority: boolean;
|
||||||
start_date: boolean;
|
start_date: boolean;
|
||||||
target_date: boolean;
|
due_date: boolean;
|
||||||
cycle: boolean;
|
cycle: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user