diff --git a/web/components/project/index.ts b/web/components/project/index.ts index 329e826a8..cd346eada 100644 --- a/web/components/project/index.ts +++ b/web/components/project/index.ts @@ -4,7 +4,7 @@ export * from "./sidebar-list"; export * from "./settings-sidebar"; export * from "./single-integration-card"; export * from "./single-project-card"; -export * from "./single-sidebar-project"; +export * from "./sidebar-list-item"; export * from "./confirm-project-leave-modal"; export * from "./member-select"; export * from "./members-select"; diff --git a/web/components/project/single-sidebar-project.tsx b/web/components/project/sidebar-list-item.tsx similarity index 85% rename from web/components/project/single-sidebar-project.tsx rename to web/components/project/sidebar-list-item.tsx index d43a82064..514efaf9c 100644 --- a/web/components/project/single-sidebar-project.tsx +++ b/web/components/project/sidebar-list-item.tsx @@ -45,7 +45,6 @@ type Props = { snapshot?: DraggableStateSnapshot; handleDeleteProject: () => void; handleCopyText: () => void; - handleProjectLeave: () => void; shortContextMenu?: boolean; }; @@ -82,7 +81,7 @@ const navigation = (workspaceSlug: string, projectId: string) => [ }, ]; -export const SingleSidebarProject: React.FC = observer((props) => { +export const ProjectSidebarListItem: React.FC = observer((props) => { const { project, sidebarCollapse, @@ -90,62 +89,41 @@ export const SingleSidebarProject: React.FC = observer((props) => { snapshot, handleDeleteProject, handleCopyText, - handleProjectLeave, shortContextMenu = false, } = props; - - const store: RootStore = useMobxStore(); - const { projectPublish, project: projectStore } = store; - + // store + const { projectPublish, project: projectStore }: RootStore = useMobxStore(); + // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - + // toast const { setToastAlert } = useToast(); const isAdmin = project.member_role === 20; - const isViewerOrGuest = project.member_role === 10 || project.member_role === 5; const handleAddToFavorites = () => { if (!workspaceSlug) return; - mutate( - 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.", - }) - ); + projectStore.addProjectToFavorites(workspaceSlug.toString(), 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( - 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(() => + projectStore.removeProjectFromFavorites(workspaceSlug.toString(), project.id).catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Couldn't remove the project from favorites. Please try again.", - }) - ); + }); + }); }; return ( @@ -159,11 +137,7 @@ export const SingleSidebarProject: React.FC = observer((props) => { > {provided && ( )} - + = observer((props) => { )} {!sidebarCollapse && ( -

- {project.name} -

+

{project.name}

)} {!sidebarCollapse && ( @@ -265,9 +232,7 @@ export const SingleSidebarProject: React.FC = observer((props) => { {/* publish project settings */} {isAdmin && ( - projectPublish.handleProjectModal(project?.id)} - > + projectPublish.handleProjectModal(project?.id)}>
@@ -279,9 +244,7 @@ export const SingleSidebarProject: React.FC = observer((props) => { {project.archive_in > 0 && ( - router.push(`/${workspaceSlug}/projects/${project?.id}/archived-issues/`) - } + onClick={() => router.push(`/${workspaceSlug}/projects/${project?.id}/archived-issues/`)} >
@@ -290,18 +253,14 @@ export const SingleSidebarProject: React.FC = observer((props) => { )} - router.push(`/${workspaceSlug}/projects/${project?.id}/draft-issues`) - } + onClick={() => router.push(`/${workspaceSlug}/projects/${project?.id}/draft-issues`)} >
Draft Issues
- router.push(`/${workspaceSlug}/projects/${project?.id}/settings`)} - > + router.push(`/${workspaceSlug}/projects/${project?.id}/settings`)}>
Settings diff --git a/web/components/project/sidebar-list.tsx b/web/components/project/sidebar-list.tsx index a46a97f04..6b672a41e 100644 --- a/web/components/project/sidebar-list.tsx +++ b/web/components/project/sidebar-list.tsx @@ -1,18 +1,15 @@ import React, { useState, FC, useRef, useEffect } from "react"; - import { useRouter } from "next/router"; -import { mutate } from "swr"; - -// react-beautiful-dnd +import useSWR, { mutate } from "swr"; import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd"; -// headless ui import { Disclosure, Transition } from "@headlessui/react"; +import { observer } from "mobx-react-lite"; // hooks import useToast from "hooks/use-toast"; import useUserAuth from "hooks/use-user-auth"; import useProjects from "hooks/use-projects"; // components -import { CreateProjectModal, DeleteProjectModal, SingleSidebarProject } from "components/project"; +import { CreateProjectModal, DeleteProjectModal, ProjectSidebarListItem } from "components/project"; // services import projectService from "services/project.service"; // icons @@ -28,9 +25,17 @@ import { PROJECTS_LIST } from "constants/fetch-keys"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; -export const ProjectSidebarList: FC = () => { - const store: any = useMobxStore(); - +export const ProjectSidebarList: FC = observer(() => { + const { theme: themeStore, workspace: workspaceStore } = useMobxStore(); + // router + const router = useRouter(); + const { workspaceSlug } = router.query; + // swr + useSWR( + workspaceSlug ? "PROJECTS_LIST" : null, + workspaceSlug ? () => workspaceStore.getWorkspaceProjects(workspaceSlug?.toString()) : null + ); + // states const [isFavoriteProjectCreate, setIsFavoriteProjectCreate] = useState(false); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); const [deleteProjectModal, setDeleteProjectModal] = useState(false); @@ -42,16 +47,14 @@ export const ProjectSidebarList: FC = () => { const containerRef = useRef(null); - const router = useRouter(); - const { workspaceSlug } = router.query; - const { user } = useUserAuth(); const { setToastAlert } = useToast(); - const { projects: allProjects } = useProjects(); + const joinedProjects = workspaceSlug && workspaceStore.workspaceJoinedProjects; + const favoriteProjects = workspaceSlug && workspaceStore.workspaceFavoriteProjects; - const joinedProjects = allProjects?.filter((p) => p.is_member); - const favoriteProjects = allProjects?.filter((p) => p.is_favorite); + console.log("workspaceJoinedProjects", workspaceStore.workspaceJoinedProjects); + console.log("workspaceFavoriteProjects", workspaceStore.workspaceFavoriteProjects); const orderedJoinedProjects: IProject[] | undefined = joinedProjects ? orderArrayBy(joinedProjects, "sort_order", "ascending") @@ -67,8 +70,7 @@ export const ProjectSidebarList: FC = () => { }; const handleCopyText = (projectId: string) => { - const originURL = - typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues`).then(() => { setToastAlert({ type: "success", @@ -86,9 +88,7 @@ export const ProjectSidebarList: FC = () => { if (source.index === destination.index) return; const projectsList = - (destination.droppableId === "joined-projects" - ? orderedJoinedProjects - : orderedFavProjects) ?? []; + (destination.droppableId === "joined-projects" ? orderedJoinedProjects : orderedFavProjects) ?? []; let updatedSortOrder = projectsList[source.index].sort_order; @@ -109,9 +109,7 @@ export const ProjectSidebarList: FC = () => { PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }), (prevData) => { if (!prevData) return prevData; - return prevData.map((p) => - p.id === draggableId ? { ...p, sort_order: updatedSortOrder } : p - ); + return prevData.map((p) => (p.id === draggableId ? { ...p, sort_order: updatedSortOrder } : p)); }, false ); @@ -176,7 +174,7 @@ export const ProjectSidebarList: FC = () => { {({ open }) => ( <> - {!store?.theme?.sidebarCollapsed && ( + {!themeStore?.sidebarCollapsed && (
{ > {(provided, snapshot) => (
- handleDeleteProject(project)} handleCopyText={() => handleCopyText(project.id)} - handleProjectLeave={() => setProjectToLeaveId(project.id)} shortContextMenu />
@@ -243,7 +240,7 @@ export const ProjectSidebarList: FC = () => { {({ open }) => ( <> - {!store?.theme?.sidebarCollapsed && ( + {!themeStore?.sidebarCollapsed && (
{ {(provided, snapshot) => (
- handleDeleteProject(project)} - handleProjectLeave={() => setProjectToLeaveId(project.id)} handleCopyText={() => handleCopyText(project.id)} />
@@ -318,10 +314,10 @@ export const ProjectSidebarList: FC = () => { }} > - {!store?.theme?.sidebarCollapsed && "Add Project"} + {!themeStore?.sidebarCollapsed && "Add Project"} )}
); -}; +}); diff --git a/web/lib/mobx/store-init.tsx b/web/lib/mobx/store-init.tsx index 5c2ee0558..5a0bcd6ca 100644 --- a/web/lib/mobx/store-init.tsx +++ b/web/lib/mobx/store-init.tsx @@ -3,19 +3,19 @@ import { useEffect } from "react"; import { useTheme } from "next-themes"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; +import { useRouter } from "next/router"; const MobxStoreInit = () => { - const store: any = useMobxStore(); + const { theme: themeStore, user: userStore, workspace: workspaceStore, project: projectStore } = useMobxStore(); const { setTheme } = useTheme(); + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + useEffect(() => { // sidebar collapsed toggle - if ( - localStorage && - localStorage.getItem("app_sidebar_collapsed") && - store?.theme?.sidebarCollapsed === null - ) - store.theme.setSidebarCollapsed( + if (localStorage && localStorage.getItem("app_sidebar_collapsed") && themeStore?.sidebarCollapsed === null) + themeStore.setSidebarCollapsed( localStorage.getItem("app_sidebar_collapsed") ? localStorage.getItem("app_sidebar_collapsed") === "true" ? true @@ -24,25 +24,30 @@ const MobxStoreInit = () => { ); // theme - if (store.theme.theme === null && store?.user?.currentUserSettings) { + if (themeStore.theme === null && userStore?.currentUserSettings) { let currentTheme = localStorage.getItem("theme"); currentTheme = currentTheme ? currentTheme : "system"; // validating the theme and applying for initial state if (currentTheme) { setTheme(currentTheme); - store.theme.setTheme({ theme: { theme: currentTheme } }); + themeStore.setTheme({ theme: { theme: currentTheme } }); } } - }, [store?.theme, store?.user, setTheme]); + }, [themeStore, userStore, setTheme]); useEffect(() => { // current user - if (store?.user?.currentUser === null) store.user.setCurrentUser(); + if (userStore?.currentUser === null) userStore.setCurrentUser(); // current user settings - if (store?.user?.currentUserSettings === null) store.user.setCurrentUserSettings(); - }, [store?.user]); + if (userStore?.currentUserSettings === null) userStore.setCurrentUserSettings(); + }, [userStore]); + + useEffect(() => { + if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString()); + if (projectId) projectStore.setProjectId(projectId.toString()); + }, [workspaceSlug, projectId, workspaceStore, projectStore]); return <>; }; diff --git a/web/services/project.service.ts b/web/services/project.service.ts index 5275349b5..1e0aaa7a3 100644 --- a/web/services/project.service.ts +++ b/web/services/project.service.ts @@ -300,13 +300,8 @@ export class ProjectService extends APIService { }); } - async addProjectToFavorites( - workspaceSlug: string, - data: { - project: string; - } - ): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/user-favorite-projects/`, data) + async addProjectToFavorites(workspaceSlug: string, project: string): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/user-favorite-projects/`, { project }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/web/store/cycles.ts b/web/store/cycles.ts index 5530548c4..d072ea3c5 100644 --- a/web/store/cycles.ts +++ b/web/store/cycles.ts @@ -4,21 +4,24 @@ import { RootStore } from "./root"; // services import { ProjectService } from "services/project.service"; import { IssueService } from "services/issue.service"; +import { ICycle } from "types"; export interface ICycleStore { loader: boolean; error: any | null; - cycleId: string | null; - - setCycleId: (cycleSlug: string) => void; + cycles: { + [cycle_id: string]: ICycle; + }; } class CycleStore implements ICycleStore { loader: boolean = false; error: any | null = null; - cycleId: string | null = null; + cycles: { + [cycle_id: string]: ICycle; + } = {}; // root store rootStore; @@ -31,12 +34,11 @@ class CycleStore implements ICycleStore { loader: observable, error: observable.ref, - cycleId: observable.ref, + cycles: observable.ref, // computed // actions - setCycleId: action, }); this.rootStore = _rootStore; @@ -47,9 +49,6 @@ class CycleStore implements ICycleStore { // computed // actions - setCycleId = (cycleSlug: string) => { - this.cycleId = cycleSlug ?? null; - }; } export default CycleStore; diff --git a/web/store/project.ts b/web/store/project.ts index 6fbf1d647..fd290d28d 100644 --- a/web/store/project.ts +++ b/web/store/project.ts @@ -62,7 +62,10 @@ export interface IProjectStore { fetchProjectLabels: (workspaceSlug: string, projectSlug: string) => Promise; fetchProjectMembers: (workspaceSlug: string, projectSlug: string) => Promise; - handleProjectLeaveModal: (project: IProject | null) => void; + addProjectToFavorites: (workspaceSlug: string, projectSlug: string) => Promise; + removeProjectFromFavorites: (workspaceSlug: string, projectSlug: string) => Promise; + + handleProjectLeaveModal: (project: any | null) => void; leaveProject: (workspaceSlug: string, projectSlug: string, user: any) => Promise; } @@ -144,6 +147,9 @@ class ProjectStore implements IProjectStore { fetchProjectLabels: action, fetchProjectMembers: action, + addProjectToFavorites: action, + removeProjectFromFavorites: action, + handleProjectLeaveModal: action, leaveProject: action, }); @@ -392,6 +398,29 @@ class ProjectStore implements IProjectStore { } }; + addProjectToFavorites = async (workspaceSlug: string, projectId: string) => { + try { + const response = await this.projectService.addProjectToFavorites(workspaceSlug, projectId); + console.log("res", response); + await this.rootStore.workspace.getWorkspaceProjects(workspaceSlug); + return response; + } catch (error) { + console.log("Failed to add project to favorite"); + throw error; + } + }; + + removeProjectFromFavorites = async (workspaceSlug: string, projectId: string) => { + try { + const response = this.projectService.removeProjectFromFavorites(workspaceSlug, projectId); + this.rootStore.workspace.getWorkspaceProjects(workspaceSlug); + return response; + } catch (error) { + console.log("Failed to add project to favorite"); + throw error; + } + }; + handleProjectLeaveModal = (project: IProject | null = null) => { if (project && project?.id) { this.projectLeaveModal = !this.projectLeaveModal; diff --git a/web/store/theme.ts b/web/store/theme.ts index c4b52c6e5..74bc7c70e 100644 --- a/web/store/theme.ts +++ b/web/store/theme.ts @@ -28,8 +28,7 @@ class ThemeStore { setSidebarCollapsed(collapsed: boolean | null = null) { if (collapsed === null) { - let _sidebarCollapsed: string | boolean | null = - localStorage.getItem("app_sidebar_collapsed"); + let _sidebarCollapsed: string | boolean | null = localStorage.getItem("app_sidebar_collapsed"); _sidebarCollapsed = _sidebarCollapsed ? (_sidebarCollapsed === "true" ? true : false) : false; this.sidebarCollapsed = _sidebarCollapsed; } else { @@ -38,7 +37,7 @@ class ThemeStore { } } - setTheme = async (_theme: { theme: ICurrentUserSettings }) => { + setTheme = async (_theme: { theme: any }) => { try { const currentTheme: string = _theme?.theme?.theme?.toString(); diff --git a/web/store/workspace.ts b/web/store/workspace.ts index 5f27ab2f7..86f35a5ab 100644 --- a/web/store/workspace.ts +++ b/web/store/workspace.ts @@ -17,12 +17,14 @@ export interface IWorkspaceStore { workspaceSlug: string | null; // computed currentWorkspace: IWorkspace | null; - workspaceLabels: IIssueLabels[] | null; + workspaceLabels: IIssueLabels[]; + workspaceJoinedProjects: IProject[]; + workspaceFavoriteProjects: IProject[]; // actions setWorkspaceSlug: (workspaceSlug: string) => void; getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null; getWorkspaceLabelById: (workspaceSlug: string, labelId: string) => IIssueLabels | null; - getWorkspaceProjects: (workspaceSlug: string) => IProject[]; + getWorkspaceProjects: (workspaceSlug: string) => void; fetchWorkspaces: () => Promise; fetchWorkspaceLabels: (workspaceSlug: string) => Promise; } @@ -44,15 +46,18 @@ class WorkspaceStore implements IWorkspaceStore { constructor(_rootStore: RootStore) { makeObservable(this, { - loader: observable, + loader: observable.ref, error: observable.ref, // objects workspaces: observable.ref, labels: observable.ref, workspaceSlug: observable.ref, + projects: observable.ref, // computed currentWorkspace: computed, workspaceLabels: computed, + workspaceJoinedProjects: computed, + workspaceFavoriteProjects: computed, // actions setWorkspaceSlug: action, getWorkspaceBySlug: action, @@ -82,7 +87,17 @@ class WorkspaceStore implements IWorkspaceStore { get workspaceLabels() { if (!this.workspaceSlug) return []; const _labels = this.labels?.[this.workspaceSlug]; - return _labels && Object.keys(_labels).length > 0 ? _labels : null; + return _labels && Object.keys(_labels).length > 0 ? _labels : []; + } + + get workspaceJoinedProjects() { + if (!this.workspaceSlug) return []; + return this.projects?.[this.workspaceSlug]?.filter((p) => p.is_member); + } + + get workspaceFavoriteProjects() { + if (!this.workspaceSlug) return []; + return this.projects?.[this.workspaceSlug]?.filter((p) => p.is_favorite); } /** @@ -96,17 +111,25 @@ class WorkspaceStore implements IWorkspaceStore { * fetch workspace info from the array of workspaces in the store. * @param workspaceSlug */ - getWorkspaceBySlug = (workspaceSlug: string) => { - return this.workspaces.find((w) => w.slug == workspaceSlug) || null; - }; + getWorkspaceBySlug = (workspaceSlug: string) => this.workspaces.find((w) => w.slug == workspaceSlug) || null; /** * get Workspace projects using workspace slug * @param workspaceSlug * @returns */ - getWorkspaceProjects = (workspaceSlug: string) => { - return this.projects[workspaceSlug]; + getWorkspaceProjects = async (workspaceSlug: string) => { + try { + const projects = await this.projectService.getProjects(workspaceSlug, { is_favorite: "all" }); + runInAction(() => { + this.projects = { + ...this.projects, + [workspaceSlug]: projects, + }; + }); + } catch (error) { + console.log("Failed to fetch project from workspace store"); + } }; /** @@ -114,9 +137,8 @@ class WorkspaceStore implements IWorkspaceStore { * @param labelId * @returns */ - getWorkspaceLabelById = (workspaceSlug: string, labelId: string) => { - return this.labels?.[workspaceSlug].find((label) => label.id === labelId) || null; - }; + getWorkspaceLabelById = (workspaceSlug: string, labelId: string) => + this.labels?.[workspaceSlug].find((label) => label.id === labelId) || null; /** * fetch user workspaces from API