misc: added favorites to app layout selection

This commit is contained in:
Sheen Capadngan
2024-06-29 01:02:28 +08:00
parent 11d9166684
commit 5a01edae7a
2 changed files with 123 additions and 35 deletions
+71 -10
View File
@@ -5,13 +5,14 @@
/* eslint-disable no-var */ /* eslint-disable no-var */
/* eslint-disable func-names */ /* eslint-disable func-names */
import { useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import { faStar } from "@fortawesome/free-regular-svg-icons";
import { import {
faAngleDown, faAngleDown,
faArrowLeft, faArrowLeft,
@@ -23,7 +24,8 @@ import {
faInfo, faInfo,
faMobile, faMobile,
faPlus, faPlus,
faQuestion faQuestion,
faStar as faSolidStar
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
@@ -72,6 +74,8 @@ import {
useRegisterUserAction, useRegisterUserAction,
useSelectOrganization useSelectOrganization
} from "@app/hooks/api"; } from "@app/hooks/api";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
import { navigateUserToOrg } from "@app/views/Login/Login.utils"; import { navigateUserToOrg } from "@app/views/Login/Login.utils";
import { CreateOrgModal } from "@app/views/Org/components"; import { CreateOrgModal } from "@app/views/Org/components";
@@ -122,6 +126,11 @@ export const AppLayout = ({ children }: LayoutProps) => {
const { workspaces, currentWorkspace } = useWorkspace(); const { workspaces, currentWorkspace } = useWorkspace();
const { orgs, currentOrg } = useOrganization(); const { orgs, currentOrg } = useOrganization();
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 { user } = useUser(); const { user } = useUser();
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
@@ -271,6 +280,30 @@ export const AppLayout = ({ children }: LayoutProps) => {
} }
}; };
const addProjectToFavorites = useCallback(
(projectId: string) => {
if (currentOrg?.id) {
updateUserProjectFavorites({
orgId: currentOrg?.id,
projectFavorites: [...(projectFavorites || []), projectId]
});
}
},
[currentOrg, projectFavorites]
);
const removeProjectFromFavorites = useCallback(
(projectId: string) => {
if (currentOrg?.id) {
updateUserProjectFavorites({
orgId: currentOrg?.id,
projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)]
});
}
},
[currentOrg, projectFavorites]
);
return ( return (
<> <>
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
@@ -451,19 +484,47 @@ export const AppLayout = ({ children }: LayoutProps) => {
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 z-50 max-h-96 border-gray-700" dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 z-50 max-h-96 border-gray-700"
> >
<div className="no-scrollbar::-webkit-scrollbar h-full no-scrollbar"> <div className="no-scrollbar::-webkit-scrollbar h-full no-scrollbar">
{workspaces {[...favoriteWorkspaces, ...nonFavoriteWorkspaces]
.filter((ws) => ws.orgId === currentOrg?.id) .filter((ws) => ws.orgId === currentOrg?.id)
.map(({ id, name }) => ( .map(({ id, name }) => (
<SelectItem <div
key={`ws-layout-list-${id}`}
value={id}
className={twMerge( className={twMerge(
currentWorkspace?.id === id && "bg-mineshaft-600", "mb-1 grid grid-cols-7 rounded-md hover:bg-mineshaft-500",
"truncate" id === currentWorkspace?.id && "bg-mineshaft-500"
)} )}
key={id}
> >
{name} <div className="col-span-6">
</SelectItem> <SelectItem
key={`ws-layout-list-${id}`}
value={id}
className="transition-none data-[highlighted]:bg-mineshaft-500"
>
{name}
</SelectItem>
</div>
<div className="col-span-1 flex items-center">
{projectFavorites?.includes(id) ? (
<FontAwesomeIcon
icon={faSolidStar}
className="text-sm text-mineshaft-300 hover:text-mineshaft-400"
onClick={(e) => {
e.stopPropagation();
removeProjectFromFavorites(id);
}}
/>
) : (
<FontAwesomeIcon
icon={faStar}
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={(e) => {
e.stopPropagation();
addProjectToFavorites(id);
}}
/>
)}
</div>
</div>
))} ))}
</div> </div>
<hr className="mt-1 mb-1 h-px border-0 bg-gray-700" /> <hr className="mt-1 mb-1 h-px border-0 bg-gray-700" />
+52 -25
View File
@@ -619,7 +619,7 @@ const OrganizationPage = withPermission(
{isFavorite ? ( {isFavorite ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faSolidStar} icon={faSolidStar}
className="text-sm text-mineshaft-300" className="text-sm text-mineshaft-300 hover:text-mineshaft-400"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
removeProjectFromFavorites(workspace.id); removeProjectFromFavorites(workspace.id);
@@ -628,7 +628,7 @@ const OrganizationPage = withPermission(
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faStar} icon={faStar}
className="text-sm text-mineshaft-400" className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
addProjectToFavorites(workspace.id); addProjectToFavorites(workspace.id);
@@ -651,6 +651,49 @@ const OrganizationPage = withPermission(
</div> </div>
); );
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
<div
onClick={() => {
router.push(`/project/${workspace.id}/secrets/overview`);
localStorage.setItem("projectData.id", workspace.id);
}}
key={workspace.id}
className={`min-w-72 group grid h-14 cursor-pointer grid-cols-6 border-t border-l border-r border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
index === 0 && "rounded-t-md"
} ${index === filteredWorkspaces.length - 1 && "rounded-b-md border-b"}`}
>
<div className="flex items-center sm:col-span-3 lg:col-span-4">
<FontAwesomeIcon icon={faFileShield} className="text-sm text-primary/70" />
<div className="ml-5 truncate text-sm text-mineshaft-100">{workspace.name}</div>
</div>
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
<div className="text-center text-sm text-mineshaft-300">
{workspace.environments?.length || 0} environments
</div>
{isFavorite ? (
<FontAwesomeIcon
icon={faSolidStar}
className="ml-6 text-sm text-mineshaft-300 hover:text-mineshaft-400"
onClick={(e) => {
e.stopPropagation();
removeProjectFromFavorites(workspace.id);
}}
/>
) : (
<FontAwesomeIcon
icon={faStar}
className="ml-6 text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={(e) => {
e.stopPropagation();
addProjectToFavorites(workspace.id);
}}
/>
)}
</div>
</div>
);
const projectsGridView = ( const projectsGridView = (
<> <>
{favoriteWorkspaces.length > 0 && ( {favoriteWorkspaces.length > 0 && (
@@ -701,29 +744,13 @@ const OrganizationPage = withPermission(
<Skeleton className="w-full bg-mineshaft-600" /> <Skeleton className="w-full bg-mineshaft-600" />
</div> </div>
))} ))}
{filteredWorkspaces.map((workspace, ind) => ( {[...favoriteWorkspaces, ...nonFavoriteWorkspaces].map((workspace, ind) =>
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events renderProjectListItem(
<div workspace,
onClick={() => { favoriteWorkspaces.some((w) => w.id === workspace.id),
router.push(`/project/${workspace.id}/secrets/overview`); ind
localStorage.setItem("projectData.id", workspace.id); )
}} )}
key={workspace.id}
className={`min-w-72 group grid h-14 cursor-pointer grid-cols-6 border-t border-l border-r border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
ind === 0 && "rounded-t-md"
} ${ind === filteredWorkspaces.length - 1 && "rounded-b-md border-b"}`}
>
<div className="flex items-center sm:col-span-3 lg:col-span-4">
<FontAwesomeIcon icon={faFileShield} className="text-sm text-primary/70" />
<div className="ml-5 truncate text-sm text-mineshaft-100">{workspace.name}</div>
</div>
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
<div className="text-center text-sm text-mineshaft-300">
{workspace.environments?.length || 0} environments
</div>
</div>
</div>
))}
</div> </div>
); );