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:
Aaryan Khandelwal 2023-07-13 19:44:53 +05:30 committed by GitHub
parent 7361657660
commit c9a5893c3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 251 additions and 308 deletions

View File

@ -7,7 +7,6 @@ import useSWR, { mutate } from "swr";
// headless ui // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// services // services
import projectService from "services/project.service";
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
import inboxServices from "services/inbox.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 useToast from "hooks/use-toast";
import useInboxView from "hooks/use-inbox-view"; import useInboxView from "hooks/use-inbox-view";
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
import useProjects from "hooks/use-projects";
// components // components
import { IssueForm } from "components/issues"; import { IssueForm } from "components/issues";
// types // types
@ -27,7 +27,6 @@ import {
PROJECT_ISSUES_DETAILS, PROJECT_ISSUES_DETAILS,
PROJECT_ISSUES_LIST, PROJECT_ISSUES_LIST,
USER_ISSUE, USER_ISSUE,
PROJECTS_LIST,
SUB_ISSUES, SUB_ISSUES,
PROJECT_ISSUES_LIST_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS,
CYCLE_ISSUES_WITH_PARAMS, CYCLE_ISSUES_WITH_PARAMS,
@ -83,6 +82,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
const { params: spreadsheetParams } = useSpreadsheetIssuesView(); const { params: spreadsheetParams } = useSpreadsheetIssuesView();
const { user } = useUser(); const { user } = useUser();
const { projects } = useProjects();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string }; if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
@ -102,11 +103,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
: null : null
); );
const { data: projects } = useSWR(
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null,
workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null
);
useEffect(() => { useEffect(() => {
if (projects && projects.length > 0) if (projects && projects.length > 0)
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null); setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);

View File

@ -1,15 +1,9 @@
import { useRouter } from "next/router"; // hooks
import useProjects from "hooks/use-projects";
import useSWR from "swr";
// ui // ui
import { CustomSelect } from "components/ui"; import { CustomSelect } from "components/ui";
// icons // icons
import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline"; 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 { export interface IssueProjectSelectProps {
value: string; value: string;
@ -22,14 +16,7 @@ export const IssueProjectSelect: React.FC<IssueProjectSelectProps> = ({
onChange, onChange,
setActiveProject, setActiveProject,
}) => { }) => {
const router = useRouter(); const { projects } = useProjects();
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)
);
return ( return (
<CustomSelect <CustomSelect

View File

@ -123,7 +123,7 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
.createProject(workspaceSlug as string, payload, user) .createProject(workspaceSlug as string, payload, user)
.then((res) => { .then((res) => {
mutate<IProject[]>( mutate<IProject[]>(
PROJECTS_LIST(workspaceSlug as string), PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
(prevData) => [res, ...(prevData ?? [])], (prevData) => [res, ...(prevData ?? [])],
false false
); );

View File

@ -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"; import { mutate } from "swr";
@ -13,7 +15,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui // ui
import { DangerButton, Input, SecondaryButton } from "components/ui"; import { DangerButton, Input, SecondaryButton } from "components/ui";
// types // types
import type { ICurrentUserResponse, IProject, IWorkspace } from "types"; import type { ICurrentUserResponse, IProject } from "types";
// fetch-keys // fetch-keys
import { PROJECTS_LIST } from "constants/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 [confirmDeleteMyProject, setConfirmDeleteMyProject] = useState(false);
const [selectedProject, setSelectedProject] = useState<IProject | null>(null); const [selectedProject, setSelectedProject] = useState<IProject | null>(null);
const workspaceSlug = (data?.workspace as IWorkspace)?.slug; const router = useRouter();
const { workspaceSlug } = router.query;
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -64,26 +67,31 @@ export const DeleteProjectModal: React.FC<TConfirmProjectDeletionProps> = ({
}; };
const handleDeletion = async () => { const handleDeletion = async () => {
setIsDeleteLoading(true);
if (!data || !workspaceSlug || !canDelete) return; 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 await projectService
.deleteProject(workspaceSlug, data.id, user) .deleteProject(workspaceSlug as string, data.id, user)
.then(() => { .then(() => {
handleClose(); handleClose();
mutate<IProject[]>(PROJECTS_LIST(workspaceSlug), (prevData) =>
prevData?.filter((project: IProject) => project.id !== data.id)
);
if (onSuccess) onSuccess(); if (onSuccess) onSuccess();
setToastAlert({
title: "Success",
type: "success",
message: "Project deleted successfully",
});
}) })
.catch((error) => { .catch(() => setIsDeleteLoading(false));
console.log(error);
setIsDeleteLoading(false);
});
}; };
return ( return (

View File

@ -2,26 +2,19 @@ import React, { useState, FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useTheme from "hooks/use-theme"; import useTheme from "hooks/use-theme";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// services import useProjects from "hooks/use-projects";
import projectService from "services/project.service";
// components // components
import { CreateProjectModal, DeleteProjectModal, SingleSidebarProject } from "components/project"; import { DeleteProjectModal, SingleSidebarProject } from "components/project";
// ui // icons
import { Loader } from "components/ui"; import { PlusIcon } from "@heroicons/react/24/outline";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// types // types
import { IFavoriteProject, IProject } from "types"; import { IProject } from "types";
// fetch-keys
import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST } from "constants/fetch-keys";
export const ProjectSidebarList: FC = () => { export const ProjectSidebarList: FC = () => {
const [deleteProjectModal, setDeleteProjectModal] = useState(false); const [deleteProjectModal, setDeleteProjectModal] = useState(false);
@ -33,93 +26,11 @@ export const ProjectSidebarList: FC = () => {
const { user } = useUserAuth(); const { user } = useUserAuth();
// states
const [isCreateProjectModal, setCreateProjectModal] = useState(false);
// theme
const { collapsed: sidebarCollapse } = useTheme(); const { collapsed: sidebarCollapse } = useTheme();
// toast handler
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { data: favoriteProjects } = useSWR( const { projects: favoriteProjects } = useProjects(true);
workspaceSlug ? FAVORITE_PROJECTS_LIST(workspaceSlug.toString()) : null, const { projects: allProjects } = useProjects();
() => (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 handleDeleteProject = (project: IProject) => { const handleDeleteProject = (project: IProject) => {
setProjectToDelete(project); setProjectToDelete(project);
@ -140,93 +51,61 @@ export const ProjectSidebarList: FC = () => {
return ( return (
<> <>
<CreateProjectModal
isOpen={isCreateProjectModal}
setIsOpen={setCreateProjectModal}
user={user}
/>
<DeleteProjectModal <DeleteProjectModal
isOpen={deleteProjectModal} isOpen={deleteProjectModal}
onClose={() => setDeleteProjectModal(false)} onClose={() => setDeleteProjectModal(false)}
data={projectToDelete} data={projectToDelete}
user={user} 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 && ( {favoriteProjects && favoriteProjects.length > 0 && (
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2 mt-5">
{!sidebarCollapse && ( {!sidebarCollapse && (
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Favorites</h5> <h5 className="text-sm font-medium text-custom-sidebar-text-200">Favorites</h5>
)} )}
{favoriteProjects.map((favoriteProject) => { {favoriteProjects.map((project) => (
const project = favoriteProject.project_detail;
return (
<SingleSidebarProject <SingleSidebarProject
key={project.id} key={project.id}
project={project} project={project}
sidebarCollapse={sidebarCollapse} sidebarCollapse={sidebarCollapse}
handleDeleteProject={() => handleDeleteProject(project)} handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)} handleCopyText={() => handleCopyText(project.id)}
handleRemoveFromFavorites={() => handleRemoveFromFavorites(project)} shortContextMenu
/> />
); ))}
})}
</div> </div>
)} )}
{allProjects && allProjects.length > 0 && (
<div className="flex flex-col space-y-2 mt-5"> <div className="flex flex-col space-y-2 mt-5">
{!sidebarCollapse && ( {!sidebarCollapse && (
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5> <h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5>
)} )}
{projects ? ( {allProjects.map((project) => (
<>
{normalProjects.length > 0 ? (
normalProjects.map((project) => (
<SingleSidebarProject <SingleSidebarProject
key={project.id} key={project.id}
project={project} project={project}
sidebarCollapse={sidebarCollapse} sidebarCollapse={sidebarCollapse}
handleDeleteProject={() => handleDeleteProject(project)} handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)} handleCopyText={() => handleCopyText(project.id)}
handleAddToFavorites={() => handleAddToFavorites(project)}
/> />
)) ))}
) : ( </div>
<div className="space-y-3 text-center">
{!sidebarCollapse && (
<h4 className="text-sm text-custom-text-200">
You don{"'"}t have any project yet
</h4>
)} )}
{allProjects && allProjects.length === 0 && (
<button <button
type="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" className="flex w-full items-center gap-2 px-3 py-2 text-sm text-custom-sidebar-text-200 mt-5"
onClick={() => setCreateProjectModal(true)} onClick={() => {
const e = new KeyboardEvent("keydown", {
key: "p",
});
document.dispatchEvent(e);
}}
> >
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-5 w-5" />
{!sidebarCollapse && "Create Project"} {!sidebarCollapse && "Add Project"}
</button> </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>
</div> </div>
</> </>
); );

View File

@ -26,9 +26,9 @@ import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { copyTextToClipboard, truncateText } from "helpers/string.helper"; import { copyTextToClipboard, truncateText } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
// types // types
import type { IFavoriteProject, IProject } from "types"; import type { IProject } from "types";
// fetch-keys // fetch-keys
import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST } from "constants/fetch-keys"; import { PROJECTS_LIST } from "constants/fetch-keys";
export type ProjectCardProps = { export type ProjectCardProps = {
project: IProject; project: IProject;
@ -55,22 +55,23 @@ export const SingleProjectCard: React.FC<ProjectCardProps> = ({
const handleAddToFavorites = () => { const handleAddToFavorites = () => {
if (!workspaceSlug) return; 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 projectService
.addProjectToFavorites(workspaceSlug as string, { .addProjectToFavorites(workspaceSlug as string, {
project: project.id, project: project.id,
}) })
.then(() => { .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({ setToastAlert({
type: "success", type: "success",
title: "Success!", title: "Success!",
@ -89,24 +90,21 @@ export const SingleProjectCard: React.FC<ProjectCardProps> = ({
const handleRemoveFromFavorites = () => { const handleRemoveFromFavorites = () => {
if (!workspaceSlug || !project) return; if (!workspaceSlug || !project) return;
projectService
.removeProjectFromFavorites(workspaceSlug as string, project.id)
.then(() => {
mutate<IProject[]>( mutate<IProject[]>(
PROJECTS_LIST(workspaceSlug as string), PROJECTS_LIST(workspaceSlug as string, { is_favorite: true }),
(prevData) => (prevData) => (prevData ?? []).filter((p) => p.id !== project.id),
(prevData ?? []).map((p) => ({
...p,
is_favorite: p.id === project.id ? false : p.is_favorite,
})),
false false
); );
mutate<IFavoriteProject[]>( mutate<IProject[]>(
FAVORITE_PROJECTS_LIST(workspaceSlug as string), PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
(prevData) => (prevData ?? []).filter((p) => p.project !== project.id), (prevData) =>
(prevData ?? []).map((p) => (p.id === project.id ? { ...p, is_favorite: false } : p)),
false false
); );
projectService
.removeProjectFromFavorites(workspaceSlug as string, project.id)
.then(() => {
setToastAlert({ setToastAlert({
type: "success", type: "success",
title: "Success!", title: "Success!",

View File

@ -1,8 +1,14 @@
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr";
// headless ui // headless ui
import { Disclosure, Transition } from "@headlessui/react"; import { Disclosure, Transition } from "@headlessui/react";
// services
import projectService from "services/project.service";
// hooks
import useToast from "hooks/use-toast";
// ui // ui
import { CustomMenu, Icon, Tooltip } from "components/ui"; import { CustomMenu, Icon, Tooltip } from "components/ui";
// icons // icons
@ -12,14 +18,15 @@ import { truncateText } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
// types // types
import { IProject } from "types"; import { IProject } from "types";
// fetch-keys
import { PROJECTS_LIST } from "constants/fetch-keys";
type Props = { type Props = {
project: IProject; project: IProject;
sidebarCollapse: boolean; sidebarCollapse: boolean;
handleDeleteProject: () => void; handleDeleteProject: () => void;
handleCopyText: () => void; handleCopyText: () => void;
handleAddToFavorites?: () => void; shortContextMenu?: boolean;
handleRemoveFromFavorites?: () => void;
}; };
const navigation = (workspaceSlug: string, projectId: string) => [ const navigation = (workspaceSlug: string, projectId: string) => [
@ -60,12 +67,65 @@ export const SingleSidebarProject: React.FC<Props> = ({
sidebarCollapse, sidebarCollapse,
handleDeleteProject, handleDeleteProject,
handleCopyText, handleCopyText,
handleAddToFavorites, shortContextMenu = false,
handleRemoveFromFavorites,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; 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 ( return (
<Disclosure key={project?.id} defaultOpen={projectId === project?.id}> <Disclosure key={project?.id} defaultOpen={projectId === project?.id}>
{({ open }) => ( {({ open }) => (
@ -124,13 +184,15 @@ export const SingleSidebarProject: React.FC<Props> = ({
{!sidebarCollapse && ( {!sidebarCollapse && (
<CustomMenu ellipsis> <CustomMenu ellipsis>
{!shortContextMenu && (
<CustomMenu.MenuItem onClick={handleDeleteProject}> <CustomMenu.MenuItem onClick={handleDeleteProject}>
<span className="flex items-center justify-start gap-2 "> <span className="flex items-center justify-start gap-2 ">
<TrashIcon className="h-4 w-4" /> <TrashIcon className="h-4 w-4" />
<span>Delete project</span> <span>Delete project</span>
</span> </span>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
{handleAddToFavorites && ( )}
{!project.is_favorite && (
<CustomMenu.MenuItem onClick={handleAddToFavorites}> <CustomMenu.MenuItem onClick={handleAddToFavorites}>
<span className="flex items-center justify-start gap-2"> <span className="flex items-center justify-start gap-2">
<StarIcon className="h-4 w-4" /> <StarIcon className="h-4 w-4" />
@ -138,7 +200,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
</span> </span>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
)} )}
{handleRemoveFromFavorites && ( {project.is_favorite && (
<CustomMenu.MenuItem onClick={handleRemoveFromFavorites}> <CustomMenu.MenuItem onClick={handleRemoveFromFavorites}>
<span className="flex items-center justify-start gap-2"> <span className="flex items-center justify-start gap-2">
<StarIcon className="h-4 w-4" /> <StarIcon className="h-4 w-4" />

View File

@ -52,10 +52,18 @@ export const WORKSPACE_INVITATIONS = "WORKSPACE_INVITATIONS";
export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION"; export const WORKSPACE_INVITATION = "WORKSPACE_INVITATION";
export const LAST_ACTIVE_WORKSPACE_AND_PROJECTS = "LAST_ACTIVE_WORKSPACE_AND_PROJECTS"; export const LAST_ACTIVE_WORKSPACE_AND_PROJECTS = "LAST_ACTIVE_WORKSPACE_AND_PROJECTS";
export const PROJECTS_LIST = (workspaceSlug: string) => export const PROJECTS_LIST = (
`PROJECTS_LIST_${workspaceSlug.toUpperCase()}`; workspaceSlug: string,
export const FAVORITE_PROJECTS_LIST = (workspaceSlug: string) => params: {
`FAVORITE_PROJECTS_LIST_${workspaceSlug.toUpperCase()}`; 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_DETAILS = (projectId: string) => `PROJECT_DETAILS_${projectId.toUpperCase()}`;
export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId.toUpperCase()}`; export const PROJECT_MEMBERS = (projectId: string) => `PROJECT_MEMBERS_${projectId.toUpperCase()}`;

View File

@ -11,14 +11,15 @@ import { IProject } from "types";
// fetch-keys // fetch-keys
import { PROJECTS_LIST } from "constants/fetch-keys"; import { PROJECTS_LIST } from "constants/fetch-keys";
const useProjects = () => { const useProjects = (type?: "all" | boolean) => {
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// api fetching
const { data: projects, mutate: mutateProjects } = useSWR( const { data: projects, mutate: mutateProjects } = useSWR(
workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null, workspaceSlug ? PROJECTS_LIST(workspaceSlug as string, { is_favorite: type ?? "all" }) : null,
workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null workspaceSlug
? () => projectService.getProjects(workspaceSlug as string, { is_favorite: type ?? "all" })
: null
); );
const recentProjects = [...(projects ?? [])] const recentProjects = [...(projects ?? [])]

View File

@ -59,7 +59,7 @@ const ControlSettings: NextPage = () => {
} = useForm<IProject>({ defaultValues }); } = useForm<IProject>({ defaultValues });
const onSubmit = async (formData: IProject) => { const onSubmit = async (formData: IProject) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !projectDetails) return;
const payload: Partial<IProject> = { const payload: Partial<IProject> = {
default_assignee: formData.default_assignee, default_assignee: formData.default_assignee,
@ -70,7 +70,19 @@ const ControlSettings: NextPage = () => {
.updateProject(workspaceSlug as string, projectId as string, payload, user) .updateProject(workspaceSlug as string, projectId as string, payload, user)
.then((res) => { .then((res) => {
mutate(PROJECT_DETAILS(projectId as string)); 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({ setToastAlert({
title: "Success", title: "Success",

View File

@ -21,10 +21,10 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { ContrastIcon, PeopleGroupIcon, ViewListIcon, InboxIcon } from "components/icons"; import { ContrastIcon, PeopleGroupIcon, ViewListIcon, InboxIcon } from "components/icons";
import { DocumentTextIcon } from "@heroicons/react/24/outline"; import { DocumentTextIcon } from "@heroicons/react/24/outline";
// types // types
import { IFavoriteProject, IProject } from "types"; import { IProject } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // 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 = [ const featuresList = [
{ {
@ -100,17 +100,16 @@ const FeaturesSettings: NextPage = () => {
if (!workspaceSlug || !projectId || !projectDetails) return; if (!workspaceSlug || !projectId || !projectDetails) return;
if (projectDetails.is_favorite) if (projectDetails.is_favorite)
mutate<IFavoriteProject[]>( mutate<IProject[]>(
FAVORITE_PROJECTS_LIST(workspaceSlug.toString()), PROJECTS_LIST(workspaceSlug.toString(), {
is_favorite: true,
}),
(prevData) => (prevData) =>
prevData?.map((p) => { prevData?.map((p) => {
if (p.project === projectId) if (p.id === projectId)
return { return {
...p, ...p,
project_detail: {
...p.project_detail,
...formData, ...formData,
},
}; };
return p; return p;
@ -119,7 +118,9 @@ const FeaturesSettings: NextPage = () => {
); );
mutate<IProject[]>( mutate<IProject[]>(
PROJECTS_LIST(workspaceSlug.toString()), PROJECTS_LIST(workspaceSlug.toString(), {
is_favorite: "all",
}),
(prevData) => (prevData) =>
prevData?.map((p) => { prevData?.map((p) => {
if (p.id === projectId) if (p.id === projectId)
@ -147,21 +148,13 @@ const FeaturesSettings: NextPage = () => {
await projectService await projectService
.updateProject(workspaceSlug as string, projectId as string, formData, user) .updateProject(workspaceSlug as string, projectId as string, formData, user)
.then(() => { .catch(() =>
mutate(
projectDetails.is_favorite
? FAVORITE_PROJECTS_LIST(workspaceSlug.toString())
: PROJECTS_LIST(workspaceSlug.toString())
);
mutate(PROJECT_DETAILS(projectId as string));
})
.catch((err) => {
setToastAlert({ setToastAlert({
type: "error", type: "error",
title: "Error!", title: "Error!",
message: "Project feature could not be updated. Please try again.", message: "Project feature could not be updated. Please try again.",
}); })
}); );
}; };
return ( return (

View File

@ -94,7 +94,20 @@ const GeneralSettings: NextPage = () => {
(prevData) => ({ ...prevData, ...res }), (prevData) => ({ ...prevData, ...res }),
false 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({ setToastAlert({
type: "success", type: "success",
title: "Success!", title: "Success!",

View File

@ -6,7 +6,6 @@ import trackEventServices from "services/track-event.service";
import type { import type {
GithubRepositoriesResponse, GithubRepositoriesResponse,
ICurrentUserResponse, ICurrentUserResponse,
IFavoriteProject,
IProject, IProject,
IProjectBulkInviteFormData, IProjectBulkInviteFormData,
IProjectMember, IProjectMember,
@ -53,8 +52,15 @@ class ProjectServices extends APIService {
}); });
} }
async getProjects(workspaceSlug: string): Promise<IProject[]> { async getProjects(
return this.get(`/api/workspaces/${workspaceSlug}/projects/`) workspaceSlug: string,
params: {
is_favorite: "all" | boolean;
}
): Promise<IProject[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/`, {
params,
})
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response?.data; 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( async addProjectToFavorites(
workspaceSlug: string, workspaceSlug: string,
data: { data: {

View File

@ -60,18 +60,6 @@ export interface IProjectLite {
identifier: string; 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 = { type ProjectViewTheme = {
issueView: TIssueViewOptions; issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions; groupByProperty: TIssueGroupByOptions;