diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 7fc10df131..816665a467 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -5,7 +5,7 @@ /* eslint-disable no-var */ /* eslint-disable func-names */ -import { useCallback, useEffect, useMemo } from "react"; +import { useEffect, useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import Image from "next/image"; @@ -128,8 +128,16 @@ export const AppLayout = ({ children }: LayoutProps) => { const { data: projectFavorites } = useGetUserProjectFavorites(currentOrg?.id!); const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites(); - const nonFavoriteWorkspaces = workspaces.filter((w) => !projectFavorites?.includes(w.id)); - const favoriteWorkspaces = workspaces.filter((w) => projectFavorites?.includes(w.id)); + + const nonFavoriteWorkspaces = useMemo( + () => workspaces.filter((w) => !projectFavorites?.includes(w.id)), + [workspaces, projectFavorites] + ); + + const favoriteWorkspaces = useMemo( + () => workspaces.filter((w) => projectFavorites?.includes(w.id)), + [workspaces, projectFavorites] + ); const { user } = useUser(); const { subscription } = useSubscription(); @@ -280,29 +288,37 @@ export const AppLayout = ({ children }: LayoutProps) => { } }; - const addProjectToFavorites = useCallback( - (projectId: string) => { + const addProjectToFavorites = async (projectId: string) => { + try { if (currentOrg?.id) { - updateUserProjectFavorites({ + await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []), projectId] }); } - }, - [currentOrg, projectFavorites] - ); + } catch (err) { + createNotification({ + text: "Failed to add project to favorites.", + type: "error" + }); + } + }; - const removeProjectFromFavorites = useCallback( - (projectId: string) => { + const removeProjectFromFavorites = async (projectId: string) => { + try { if (currentOrg?.id) { - updateUserProjectFavorites({ + await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] }); } - }, - [currentOrg, projectFavorites] - ); + } catch (err) { + createNotification({ + text: "Failed to remove project from favorites.", + type: "error" + }); + } + }; return ( <> diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index 14fd6b5b09..2a9a6c5551 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -1,6 +1,6 @@ // REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import Head from "next/head"; @@ -489,7 +489,8 @@ const OrganizationPage = withPermission( const { currentOrg } = useOrganization(); const routerOrgId = String(router.query.id); const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === routerOrgId) || []; - const { data: projectFavorites } = useGetUserProjectFavorites(currentOrg?.id!); + const { data: projectFavorites, isLoading: isProjectFavoritesLoading } = + useGetUserProjectFavorites(currentOrg?.id!); const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites(); const addUsersToProject = useAddUserToWsNonE2EE(); @@ -575,34 +576,59 @@ const OrganizationPage = withPermission( const filteredWorkspaces = orgWorkspaces.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()) ); - const favoriteWorkspaces = filteredWorkspaces.filter((ws) => projectFavorites?.includes(ws.id)); - const nonFavoriteWorkspaces = filteredWorkspaces.filter((ws) => - favoriteWorkspaces.every((entry) => entry.id !== ws.id) + + const workspacesWithFaveProp = useMemo( + () => + filteredWorkspaces + .map((w): Workspace & { isFavorite: boolean } => ({ + ...w, + isFavorite: Boolean(projectFavorites?.includes(w.id)) + })) + .sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite)), + [filteredWorkspaces, projectFavorites] ); - const addProjectToFavorites = useCallback( - (projectId: string) => { + const favoriteWorkspaces = useMemo( + () => workspacesWithFaveProp.filter((w) => w.isFavorite), + [workspacesWithFaveProp] + ); + + const nonFavoriteWorkspaces = useMemo( + () => workspacesWithFaveProp.filter((w) => !w.isFavorite), + [workspacesWithFaveProp] + ); + + const addProjectToFavorites = async (projectId: string) => { + try { if (currentOrg?.id) { - updateUserProjectFavorites({ + await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []), projectId] }); } - }, - [currentOrg, projectFavorites] - ); + } catch (err) { + createNotification({ + text: "Failed to add project to favorites.", + type: "error" + }); + } + }; - const removeProjectFromFavorites = useCallback( - (projectId: string) => { + const removeProjectFromFavorites = async (projectId: string) => { + try { if (currentOrg?.id) { - updateUserProjectFavorites({ + await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] }); } - }, - [currentOrg, projectFavorites] - ); + } catch (err) { + createNotification({ + text: "Failed to remove project from favorites.", + type: "error" + }); + } + }; const renderProjectGridItem = (workspace: Workspace, isFavorite: boolean) => ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events @@ -709,7 +735,7 @@ const OrganizationPage = withPermission( > )}