import { ReactNode, useMemo, useState } from "react"; import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons"; import { faArrowDownAZ, faArrowRight, faArrowUpZA, faBorderAll, faList, faMagnifyingGlass, faPlus, faSearch, faStar as faSolidStar } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, IconButton, Input, Pagination, Skeleton, Tooltip } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { getProjectHomePage } from "@app/helpers/project"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetUserWorkspaces } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation"; import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries"; import { ProjectType, Workspace } from "@app/hooks/api/workspace/types"; type Props = { type: ProjectType; onAddNewProject: () => void; onUpgradePlan: () => void; isAddingProjectsAllowed: boolean; }; enum ProjectOrderBy { Name = "name" } enum ProjectsViewMode { GRID = "grid", LIST = "list" } export const MyProjectView = ({ type, onAddNewProject, onUpgradePlan, isAddingProjectsAllowed }: Props) => { const navigate = useNavigate(); const { currentOrg } = useOrganization(); const { data: workspaces = [], isPending: isWorkspaceLoading } = useGetUserWorkspaces({ type }); const { setPage, perPage, setPerPage, page, offset, limit, toggleOrderDirection, orderDirection } = usePagination(ProjectOrderBy.Name, { initPerPage: 24 }); const { data: projectFavorites, isPending: isProjectFavoritesLoading } = useGetUserProjectFavorites(currentOrg?.id); const [projectsViewMode, setProjectsViewMode] = useState( (localStorage.getItem("projectsViewMode") as ProjectsViewMode) || ProjectsViewMode.GRID ); const [searchFilter, setSearchFilter] = useState(""); const isProjectViewLoading = isWorkspaceLoading || isProjectFavoritesLoading; const isWorkspaceEmpty = !isProjectViewLoading && workspaces?.length === 0; const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites(); const filteredWorkspaces = useMemo( () => workspaces .filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())) .sort((a, b) => orderDirection === OrderByDirection.ASC ? a.name.toLowerCase().localeCompare(b.name.toLowerCase()) : b.name.toLowerCase().localeCompare(a.name.toLowerCase()) ), [searchFilter, orderDirection, workspaces] ); useResetPageHelper({ setPage, offset, totalCount: filteredWorkspaces.length }); const { workspacesWithFaveProp } = useMemo(() => { const workspacesWithFav = filteredWorkspaces .map((w): Workspace & { isFavorite: boolean } => ({ ...w, isFavorite: Boolean(projectFavorites?.includes(w.id)) })) .sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite)) .slice(offset, limit * page); return { workspacesWithFaveProp: workspacesWithFav }; }, [filteredWorkspaces, projectFavorites, offset, limit, page]); const addProjectToFavorites = async (projectId: string) => { try { if (currentOrg?.id) { await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []), projectId] }); } } catch { createNotification({ text: "Failed to add project to favorites.", type: "error" }); } }; const removeProjectFromFavorites = async (projectId: string) => { try { if (currentOrg?.id) { await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] }); } } catch { 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
{ navigate({ to: getProjectHomePage(workspace), params: { projectId: workspace.id } }); }} key={workspace.id} className="flex h-40 min-w-72 cursor-pointer flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4" >
{workspace.name}
{isFavorite ? ( { e.stopPropagation(); removeProjectFromFavorites(workspace.id); }} /> ) : ( { e.stopPropagation(); addProjectToFavorites(workspace.id); }} /> )}
{workspace.description}
{type === ProjectType.SecretManager && (
{workspace.environments?.length || 0} environments
)}
); const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
{ navigate({ to: getProjectHomePage(workspace), params: { projectId: workspace.id } }); }} key={workspace.id} className={`group grid h-14 min-w-72 cursor-pointer grid-cols-6 border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${ index === 0 && "rounded-t-md" }`} >
{workspace.name}
{type === ProjectType.SecretManager && (
{workspace.environments?.length || 0} environments
)} {isFavorite ? ( { e.stopPropagation(); removeProjectFromFavorites(workspace.id); }} /> ) : ( { e.stopPropagation(); addProjectToFavorites(workspace.id); }} /> )}
); let projectsComponents: ReactNode; if (filteredWorkspaces.length || isProjectViewLoading) { switch (projectsViewMode) { case ProjectsViewMode.GRID: projectsComponents = (
{isProjectViewLoading && Array.apply(0, Array(3)).map((_x, i) => (
))} {!isProjectViewLoading && ( <> {workspacesWithFaveProp.map((workspace) => renderProjectGridItem(workspace, workspace.isFavorite) )} )}
); break; case ProjectsViewMode.LIST: default: projectsComponents = (
{isProjectViewLoading && Array.apply(0, Array(3)).map((_x, i) => (
))} {!isProjectViewLoading && workspacesWithFaveProp.map((workspace, ind) => renderProjectListItem(workspace, workspace.isFavorite, ind) )}
); break; } } else if (workspaces.length && searchFilter) { projectsComponents = (
No projects match search...
); } return (
setSearchFilter(e.target.value)} leftIcon={} />
{ localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID); setProjectsViewMode(ProjectsViewMode.GRID); }} ariaLabel="grid" size="xs" className={`${ projectsViewMode === ProjectsViewMode.GRID ? "bg-mineshaft-500" : "bg-transparent" } min-w-[2.4rem] border-none hover:bg-mineshaft-600`} > { localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST); setProjectsViewMode(ProjectsViewMode.LIST); }} ariaLabel="list" size="xs" className={`${ projectsViewMode === ProjectsViewMode.LIST ? "bg-mineshaft-500" : "bg-transparent" } min-w-[2.4rem] border-none hover:bg-mineshaft-600`} >
{(isAllowed) => ( )}
{projectsComponents} {!isProjectViewLoading && Boolean(filteredWorkspaces.length) && ( )} {isWorkspaceEmpty && (
You are not part of any projects in this organization yet. When you are, they will appear here.
Create a new project, or ask other organization members to give you necessary permissions.
)}
); };