forked from github/plane
chore: update favorite projects endpoint (#1522)
* chore: upate favorite projects list endpoint * chore: add project button on the sidebar * refactor: sidebar favorite projects workflow
This commit is contained in:
parent
7361657660
commit
c9a5893c3f
@ -7,7 +7,6 @@ import useSWR, { mutate } from "swr";
|
||||
// headless ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
import modulesService from "services/modules.service";
|
||||
import issuesService from "services/issues.service";
|
||||
import inboxServices from "services/inbox.service";
|
||||
@ -18,6 +17,7 @@ import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useInboxView from "hooks/use-inbox-view";
|
||||
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
||||
import useProjects from "hooks/use-projects";
|
||||
// components
|
||||
import { IssueForm } from "components/issues";
|
||||
// types
|
||||
@ -27,7 +27,6 @@ import {
|
||||
PROJECT_ISSUES_DETAILS,
|
||||
PROJECT_ISSUES_LIST,
|
||||
USER_ISSUE,
|
||||
PROJECTS_LIST,
|
||||
SUB_ISSUES,
|
||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||
CYCLE_ISSUES_WITH_PARAMS,
|
||||
@ -83,6 +82,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
const { params: spreadsheetParams } = useSpreadsheetIssuesView();
|
||||
|
||||
const { user } = useUser();
|
||||
const { projects } = useProjects();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
|
||||
@ -102,11 +103,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: projects } = useSWR(
|
||||
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null,
|
||||
workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (projects && projects.length > 0)
|
||||
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||
|
@ -1,15 +1,9 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// hooks
|
||||
import useProjects from "hooks/use-projects";
|
||||
// ui
|
||||
import { CustomSelect } from "components/ui";
|
||||
// icons
|
||||
import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
// fetch-keys
|
||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||
|
||||
export interface IssueProjectSelectProps {
|
||||
value: string;
|
||||
@ -22,14 +16,7 @@ export const IssueProjectSelect: React.FC<IssueProjectSelectProps> = ({
|
||||
onChange,
|
||||
setActiveProject,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// Fetching Projects List
|
||||
const { data: projects } = useSWR(
|
||||
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null,
|
||||
() => (workspaceSlug ? projectService.getProjects(workspaceSlug as string) : null)
|
||||
);
|
||||
const { projects } = useProjects();
|
||||
|
||||
return (
|
||||
<CustomSelect
|
||||
|
@ -123,7 +123,7 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
|
||||
.createProject(workspaceSlug as string, payload, user)
|
||||
.then((res) => {
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string),
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||
(prevData) => [res, ...(prevData ?? [])],
|
||||
false
|
||||
);
|
||||
|
@ -1,4 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { mutate } from "swr";
|
||||
|
||||
@ -13,7 +15,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
// ui
|
||||
import { DangerButton, Input, SecondaryButton } from "components/ui";
|
||||
// types
|
||||
import type { ICurrentUserResponse, IProject, IWorkspace } from "types";
|
||||
import type { ICurrentUserResponse, IProject } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||
|
||||
@ -37,7 +39,8 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
|
||||
const [confirmDeleteMyProject, setConfirmDeleteMyProject] = useState(false);
|
||||
const [selectedProject, setSelectedProject] = useState<IProject | null>(null);
|
||||
|
||||
const workspaceSlug = (data?.workspace as IWorkspace)?.slug;
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -64,26 +67,31 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
|
||||
};
|
||||
|
||||
const handleDeletion = async () => {
|
||||
setIsDeleteLoading(true);
|
||||
if (!data || !workspaceSlug || !canDelete) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
if (data.is_favorite)
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: true }),
|
||||
(prevData) => prevData?.filter((project: IProject) => project.id !== data.id),
|
||||
false
|
||||
);
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||
(prevData) => prevData?.filter((project: IProject) => project.id !== data.id),
|
||||
false
|
||||
);
|
||||
|
||||
await projectService
|
||||
.deleteProject(workspaceSlug, data.id, user)
|
||||
.deleteProject(workspaceSlug as string, data.id, user)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
mutate<IProject[]>(PROJECTS_LIST(workspaceSlug), (prevData) =>
|
||||
prevData?.filter((project: IProject) => project.id !== data.id)
|
||||
);
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Project deleted successfully",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setIsDeleteLoading(false);
|
||||
});
|
||||
.catch(() => setIsDeleteLoading(false));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -2,26 +2,19 @@ import React, { useState, FC } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useTheme from "hooks/use-theme";
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
import useProjects from "hooks/use-projects";
|
||||
// components
|
||||
import { CreateProjectModal, DeleteProjectModal, SingleSidebarProject } from "components/project";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
import { DeleteProjectModal, SingleSidebarProject } from "components/project";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// helpers
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
// types
|
||||
import { IFavoriteProject, IProject } from "types";
|
||||
// fetch-keys
|
||||
import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST } from "constants/fetch-keys";
|
||||
import { IProject } from "types";
|
||||
|
||||
export const ProjectSidebarList: FC = () => {
|
||||
const [deleteProjectModal, setDeleteProjectModal] = useState(false);
|
||||
@ -33,93 +26,11 @@ export const ProjectSidebarList: FC = () => {
|
||||
|
||||
const { user } = useUserAuth();
|
||||
|
||||
// states
|
||||
const [isCreateProjectModal, setCreateProjectModal] = useState(false);
|
||||
// theme
|
||||
const { collapsed: sidebarCollapse } = useTheme();
|
||||
// toast handler
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { data: favoriteProjects } = useSWR(
|
||||
workspaceSlug ? FAVORITE_PROJECTS_LIST(workspaceSlug.toString()) : null,
|
||||
() => (workspaceSlug ? projectService.getFavoriteProjects(workspaceSlug.toString()) : null)
|
||||
);
|
||||
|
||||
const { data: projects } = useSWR(
|
||||
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null,
|
||||
() => (workspaceSlug ? projectService.getProjects(workspaceSlug as string) : null)
|
||||
);
|
||||
const normalProjects = projects?.filter((p) => !p.is_favorite) ?? [];
|
||||
|
||||
const handleAddToFavorites = (project: IProject) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
projectService
|
||||
.addProjectToFavorites(workspaceSlug as string, {
|
||||
project: project.id,
|
||||
})
|
||||
.then(() => {
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => ({
|
||||
...p,
|
||||
is_favorite: p.id === project.id ? true : p.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
mutate(FAVORITE_PROJECTS_LIST(workspaceSlug as string));
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Successfully added the project to favorites.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't remove the project from favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveFromFavorites = (project: IProject) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
projectService
|
||||
.removeProjectFromFavorites(workspaceSlug as string, project.id)
|
||||
.then(() => {
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => ({
|
||||
...p,
|
||||
is_favorite: p.id === project.id ? false : p.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
mutate<IFavoriteProject[]>(
|
||||
FAVORITE_PROJECTS_LIST(workspaceSlug as string),
|
||||
(prevData) => (prevData ?? []).filter((p) => p.project !== project.id),
|
||||
false
|
||||
);
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Successfully removed the project from favorites.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't remove the project from favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
const { projects: favoriteProjects } = useProjects(true);
|
||||
const { projects: allProjects } = useProjects();
|
||||
|
||||
const handleDeleteProject = (project: IProject) => {
|
||||
setProjectToDelete(project);
|
||||
@ -140,93 +51,61 @@ export const ProjectSidebarList: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreateProjectModal
|
||||
isOpen={isCreateProjectModal}
|
||||
setIsOpen={setCreateProjectModal}
|
||||
user={user}
|
||||
/>
|
||||
<DeleteProjectModal
|
||||
isOpen={deleteProjectModal}
|
||||
onClose={() => setDeleteProjectModal(false)}
|
||||
data={projectToDelete}
|
||||
user={user}
|
||||
/>
|
||||
<div className="h-full overflow-y-auto mt-5 px-4">
|
||||
<div className="h-full overflow-y-auto px-4">
|
||||
{favoriteProjects && favoriteProjects.length > 0 && (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="flex flex-col space-y-2 mt-5">
|
||||
{!sidebarCollapse && (
|
||||
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Favorites</h5>
|
||||
)}
|
||||
{favoriteProjects.map((favoriteProject) => {
|
||||
const project = favoriteProject.project_detail;
|
||||
|
||||
return (
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={sidebarCollapse}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
handleRemoveFromFavorites={() => handleRemoveFromFavorites(project)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{favoriteProjects.map((project) => (
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={sidebarCollapse}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
shortContextMenu
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col space-y-2 mt-5">
|
||||
{!sidebarCollapse && (
|
||||
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5>
|
||||
)}
|
||||
{projects ? (
|
||||
<>
|
||||
{normalProjects.length > 0 ? (
|
||||
normalProjects.map((project) => (
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={sidebarCollapse}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
handleAddToFavorites={() => handleAddToFavorites(project)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="space-y-3 text-center">
|
||||
{!sidebarCollapse && (
|
||||
<h4 className="text-sm text-custom-text-200">
|
||||
You don{"'"}t have any project yet
|
||||
</h4>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="group flex w-full items-center justify-center gap-2 rounded-md bg-custom-background-80 p-2 text-xs text-custom-text-100"
|
||||
onClick={() => setCreateProjectModal(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
{!sidebarCollapse && "Create Project"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full">
|
||||
<Loader className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Loader.Item height="30px" />
|
||||
<Loader.Item height="15px" width="80%" />
|
||||
<Loader.Item height="15px" width="80%" />
|
||||
<Loader.Item height="15px" width="80%" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Loader.Item height="30px" />
|
||||
<Loader.Item height="15px" width="80%" />
|
||||
<Loader.Item height="15px" width="80%" />
|
||||
<Loader.Item height="15px" width="80%" />
|
||||
</div>
|
||||
</Loader>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{allProjects && allProjects.length > 0 && (
|
||||
<div className="flex flex-col space-y-2 mt-5">
|
||||
{!sidebarCollapse && (
|
||||
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5>
|
||||
)}
|
||||
{allProjects.map((project) => (
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={sidebarCollapse}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{allProjects && allProjects.length === 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-custom-sidebar-text-200 mt-5"
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "p",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
{!sidebarCollapse && "Add Project"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -26,9 +26,9 @@ import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// types
|
||||
import type { IFavoriteProject, IProject } from "types";
|
||||
import type { IProject } from "types";
|
||||
// fetch-keys
|
||||
import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST } from "constants/fetch-keys";
|
||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||
|
||||
export type ProjectCardProps = {
|
||||
project: IProject;
|
||||
@ -55,22 +55,23 @@ export const SingleProjectCard: React.FC<ProjectCardProps> = ({
|
||||
const handleAddToFavorites = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: true }),
|
||||
(prevData) => [...(prevData ?? []), { ...project, is_favorite: true }],
|
||||
false
|
||||
);
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => (p.id === project.id ? { ...p, is_favorite: true } : p)),
|
||||
false
|
||||
);
|
||||
|
||||
projectService
|
||||
.addProjectToFavorites(workspaceSlug as string, {
|
||||
project: project.id,
|
||||
})
|
||||
.then(() => {
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => ({
|
||||
...p,
|
||||
is_favorite: p.id === project.id ? true : p.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
mutate(FAVORITE_PROJECTS_LIST(workspaceSlug as string));
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
@ -89,24 +90,21 @@ export const SingleProjectCard: React.FC<ProjectCardProps> = ({
|
||||
const handleRemoveFromFavorites = () => {
|
||||
if (!workspaceSlug || !project) return;
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: true }),
|
||||
(prevData) => (prevData ?? []).filter((p) => p.id !== project.id),
|
||||
false
|
||||
);
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => (p.id === project.id ? { ...p, is_favorite: false } : p)),
|
||||
false
|
||||
);
|
||||
|
||||
projectService
|
||||
.removeProjectFromFavorites(workspaceSlug as string, project.id)
|
||||
.then(() => {
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => ({
|
||||
...p,
|
||||
is_favorite: p.id === project.id ? false : p.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
mutate<IFavoriteProject[]>(
|
||||
FAVORITE_PROJECTS_LIST(workspaceSlug as string),
|
||||
(prevData) => (prevData ?? []).filter((p) => p.project !== project.id),
|
||||
false
|
||||
);
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
|
@ -1,8 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { mutate } from "swr";
|
||||
|
||||
// headless ui
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { CustomMenu, Icon, Tooltip } from "components/ui";
|
||||
// icons
|
||||
@ -12,14 +18,15 @@ import { truncateText } from "helpers/string.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
project: IProject;
|
||||
sidebarCollapse: boolean;
|
||||
handleDeleteProject: () => void;
|
||||
handleCopyText: () => void;
|
||||
handleAddToFavorites?: () => void;
|
||||
handleRemoveFromFavorites?: () => void;
|
||||
shortContextMenu?: boolean;
|
||||
};
|
||||
|
||||
const navigation = (workspaceSlug: string, projectId: string) => [
|
||||
@ -60,12 +67,65 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
sidebarCollapse,
|
||||
handleDeleteProject,
|
||||
handleCopyText,
|
||||
handleAddToFavorites,
|
||||
handleRemoveFromFavorites,
|
||||
shortContextMenu = false,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const handleAddToFavorites = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: true }),
|
||||
(prevData) => [...(prevData ?? []), { ...project, is_favorite: true }],
|
||||
false
|
||||
);
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => (p.id === project.id ? { ...p, is_favorite: true } : p)),
|
||||
false
|
||||
);
|
||||
|
||||
projectService
|
||||
.addProjectToFavorites(workspaceSlug as string, {
|
||||
project: project.id,
|
||||
})
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't remove the project from favorites. Please try again.",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveFromFavorites = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: true }),
|
||||
(prevData) => (prevData ?? []).filter((p) => p.id !== project.id),
|
||||
false
|
||||
);
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((p) => (p.id === project.id ? { ...p, is_favorite: false } : p)),
|
||||
false
|
||||
);
|
||||
|
||||
projectService.removeProjectFromFavorites(workspaceSlug as string, project.id).catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't remove the project from favorites. Please try again.",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Disclosure key={project?.id} defaultOpen={projectId === project?.id}>
|
||||
{({ open }) => (
|
||||
@ -124,13 +184,15 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
|
||||
{!sidebarCollapse && (
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem onClick={handleDeleteProject}>
|
||||
<span className="flex items-center justify-start gap-2 ">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span>Delete project</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
{handleAddToFavorites && (
|
||||
{!shortContextMenu && (
|
||||
<CustomMenu.MenuItem onClick={handleDeleteProject}>
|
||||
<span className="flex items-center justify-start gap-2 ">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span>Delete project</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
{!project.is_favorite && (
|
||||
<CustomMenu.MenuItem onClick={handleAddToFavorites}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<StarIcon className="h-4 w-4" />
|
||||
@ -138,7 +200,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
{handleRemoveFromFavorites && (
|
||||
{project.is_favorite && (
|
||||
<CustomMenu.MenuItem onClick={handleRemoveFromFavorites}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<StarIcon className="h-4 w-4" />
|
||||
|
@ -52,10 +52,18 @@ export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS";
|
||||
export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION";
|
||||
export const LAST_ACTIVE_WORKSPACE_AND_PROJECTS = "LAST_ACTIVE_WORKSPACE_AND_PROJECTS";
|
||||
|
||||
export const PROJECTS_LIST = (workspaceSlug: string) =>
|
||||
`PROJECTS_LIST_${workspaceSlug.toUpperCase()}`;
|
||||
export const FAVORITE_PROJECTS_LIST = (workspaceSlug: string) =>
|
||||
`FAVORITE_PROJECTS_LIST_${workspaceSlug.toUpperCase()}`;
|
||||
export const PROJECTS_LIST = (
|
||||
workspaceSlug: string,
|
||||
params: {
|
||||
is_favorite: "all" | boolean;
|
||||
}
|
||||
) => {
|
||||
if (!params) return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}`;
|
||||
|
||||
return `PROJECTS_LIST_${workspaceSlug.toUpperCase()}_${params.is_favorite
|
||||
.toString()
|
||||
.toUpperCase()}`;
|
||||
};
|
||||
export const PROJECT_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId.toUpperCase()}`;
|
||||
|
||||
export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId.toUpperCase()}`;
|
||||
|
@ -11,14 +11,15 @@ import { IProject } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||
|
||||
const useProjects = () => {
|
||||
// router
|
||||
const useProjects = (type?: "all" | boolean) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// api fetching
|
||||
|
||||
const { data: projects, mutate: mutateProjects } = useSWR(
|
||||
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null,
|
||||
workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null
|
||||
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" }) : null,
|
||||
workspaceSlug
|
||||
? () => projectService.getProjects(workspaceSlug as string, { is_favorite: type ?? "all" })
|
||||
: null
|
||||
);
|
||||
|
||||
const recentProjects = [...(projects ?? [])]
|
||||
|
@ -59,7 +59,7 @@ const ControlSettings: NextPage = () => {
|
||||
} = useForm<IProject>({ defaultValues });
|
||||
|
||||
const onSubmit = async (formData: IProject) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
if (!workspaceSlug || !projectId || !projectDetails) return;
|
||||
|
||||
const payload: Partial<IProject> = {
|
||||
default_assignee: formData.default_assignee,
|
||||
@ -70,7 +70,19 @@ const ControlSettings: NextPage = () => {
|
||||
.updateProject(workspaceSlug as string, projectId as string, payload, user)
|
||||
.then((res) => {
|
||||
mutate(PROJECT_DETAILS(projectId as string));
|
||||
mutate(PROJECTS_LIST(workspaceSlug as string));
|
||||
|
||||
if (projectDetails.is_favorite)
|
||||
mutate(
|
||||
PROJECTS_LIST(workspaceSlug as string, {
|
||||
is_favorite: true,
|
||||
})
|
||||
);
|
||||
|
||||
mutate(
|
||||
PROJECTS_LIST(workspaceSlug as string, {
|
||||
is_favorite: "all",
|
||||
})
|
||||
);
|
||||
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
|
@ -21,10 +21,10 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
import { ContrastIcon, PeopleGroupIcon, ViewListIcon, InboxIcon } from "components/icons";
|
||||
import { DocumentTextIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { IFavoriteProject, IProject } from "types";
|
||||
import { IProject } from "types";
|
||||
import type { NextPage } from "next";
|
||||
// fetch-keys
|
||||
import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
const featuresList = [
|
||||
{
|
||||
@ -100,17 +100,16 @@ const FeaturesSettings: NextPage = () => {
|
||||
if (!workspaceSlug || !projectId || !projectDetails) return;
|
||||
|
||||
if (projectDetails.is_favorite)
|
||||
mutate<IFavoriteProject[]>(
|
||||
FAVORITE_PROJECTS_LIST(workspaceSlug.toString()),
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug.toString(), {
|
||||
is_favorite: true,
|
||||
}),
|
||||
(prevData) =>
|
||||
prevData?.map((p) => {
|
||||
if (p.project === projectId)
|
||||
if (p.id === projectId)
|
||||
return {
|
||||
...p,
|
||||
project_detail: {
|
||||
...p.project_detail,
|
||||
...formData,
|
||||
},
|
||||
...formData,
|
||||
};
|
||||
|
||||
return p;
|
||||
@ -119,7 +118,9 @@ const FeaturesSettings: NextPage = () => {
|
||||
);
|
||||
|
||||
mutate<IProject[]>(
|
||||
PROJECTS_LIST(workspaceSlug.toString()),
|
||||
PROJECTS_LIST(workspaceSlug.toString(), {
|
||||
is_favorite: "all",
|
||||
}),
|
||||
(prevData) =>
|
||||
prevData?.map((p) => {
|
||||
if (p.id === projectId)
|
||||
@ -147,21 +148,13 @@ const FeaturesSettings: NextPage = () => {
|
||||
|
||||
await projectService
|
||||
.updateProject(workspaceSlug as string, projectId as string, formData, user)
|
||||
.then(() => {
|
||||
mutate(
|
||||
projectDetails.is_favorite
|
||||
? FAVORITE_PROJECTS_LIST(workspaceSlug.toString())
|
||||
: PROJECTS_LIST(workspaceSlug.toString())
|
||||
);
|
||||
mutate(PROJECT_DETAILS(projectId as string));
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Project feature could not be updated. Please try again.",
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -94,7 +94,20 @@ const GeneralSettings: NextPage = () => {
|
||||
(prevData) => ({ ...prevData, ...res }),
|
||||
false
|
||||
);
|
||||
mutate(PROJECTS_LIST(workspaceSlug as string));
|
||||
|
||||
if (projectDetails.is_favorite)
|
||||
mutate(
|
||||
PROJECTS_LIST(workspaceSlug as string, {
|
||||
is_favorite: true,
|
||||
})
|
||||
);
|
||||
|
||||
mutate(
|
||||
PROJECTS_LIST(workspaceSlug as string, {
|
||||
is_favorite: "all",
|
||||
})
|
||||
);
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
|
@ -6,7 +6,6 @@ import trackEventServices from "services/track-event.service";
|
||||
import type {
|
||||
GithubRepositoriesResponse,
|
||||
ICurrentUserResponse,
|
||||
IFavoriteProject,
|
||||
IProject,
|
||||
IProjectBulkInviteFormData,
|
||||
IProjectMember,
|
||||
@ -53,8 +52,15 @@ class ProjectServices extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async getProjects(workspaceSlug: string): Promise<IProject[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/`)
|
||||
async getProjects(
|
||||
workspaceSlug: string,
|
||||
params: {
|
||||
is_favorite: "all" | boolean;
|
||||
}
|
||||
): Promise<IProject[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
@ -298,14 +304,6 @@ class ProjectServices extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async getFavoriteProjects(workspaceSlug: string): Promise<IFavoriteProject[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/user-favorite-projects/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async addProjectToFavorites(
|
||||
workspaceSlug: string,
|
||||
data: {
|
||||
|
12
apps/app/types/projects.d.ts
vendored
12
apps/app/types/projects.d.ts
vendored
@ -60,18 +60,6 @@ export interface IProjectLite {
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export interface IFavoriteProject {
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
id: string;
|
||||
project: string;
|
||||
project_detail: IProject;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
user: string;
|
||||
workspace: string;
|
||||
}
|
||||
|
||||
type ProjectViewTheme = {
|
||||
issueView: TIssueViewOptions;
|
||||
groupByProperty: TIssueGroupByOptions;
|
||||
|
Loading…
Reference in New Issue
Block a user