From f957b9d970024896a41791269848e40edc5fe6b4 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 8 May 2024 01:03:41 +0800 Subject: [PATCH] misc: migrated to react-state --- .../pages/project/[id]/secrets/overview.tsx | 5 +- .../SecretOverviewPage.store.tsx | 78 ------------------- .../SecretOverviewPage/SecretOverviewPage.tsx | 70 +++++++++++++---- .../SelectionPanel/SelectionPanel.tsx | 20 ++++- 4 files changed, 74 insertions(+), 99 deletions(-) delete mode 100644 frontend/src/views/SecretOverviewPage/SecretOverviewPage.store.tsx diff --git a/frontend/src/pages/project/[id]/secrets/overview.tsx b/frontend/src/pages/project/[id]/secrets/overview.tsx index eaf915120..69e2bc196 100644 --- a/frontend/src/pages/project/[id]/secrets/overview.tsx +++ b/frontend/src/pages/project/[id]/secrets/overview.tsx @@ -2,7 +2,6 @@ import { useTranslation } from "react-i18next"; import Head from "next/head"; import { SecretOverviewPage } from "@app/views/SecretOverviewPage"; -import { StoreProvider } from "@app/views/SecretOverviewPage/SecretOverviewPage.store"; const Dashboard = () => { const { t } = useTranslation(); @@ -17,9 +16,7 @@ const Dashboard = () => {
- - - +
); diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.store.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.store.tsx deleted file mode 100644 index db544c7e8..000000000 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.store.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { createContext, ReactNode, useContext, useEffect, useRef } from "react"; -import { useRouter } from "next/router"; -import { createStore, StateCreator, StoreApi, useStore } from "zustand"; - -export enum EntryType { - FOLDER = "folder", - SECRET = "secret" -} - -type SelectedEntriesState = { - selectedEntries: { - [EntryType.FOLDER]: Record; - [EntryType.SECRET]: Record; - }; - action: { - toggle: (type: EntryType, key: string) => void; - reset: () => void; - }; -}; - -const createSelectedSecretStore: StateCreator = (set) => ({ - selectedEntries: { - [EntryType.FOLDER]: {}, - [EntryType.SECRET]: {} - }, - action: { - toggle: (type: EntryType, key: string) => - set((state) => { - const isChecked = Boolean(state.selectedEntries[type]?.[key]); - const newChecks = { ...state.selectedEntries }; - // remove selection if its present else add it - if (isChecked) delete newChecks[type][key]; - else newChecks[type][key] = true; - return { selectedEntries: newChecks }; - }), - reset: () => - set({ - selectedEntries: { - [EntryType.FOLDER]: {}, - [EntryType.SECRET]: {} - } - }) - } -}); - -const StoreContext = createContext | null>(null); -export const StoreProvider = ({ children }: { children: ReactNode }) => { - const storeRef = useRef>(); - const router = useRouter(); - if (!storeRef.current) { - storeRef.current = createStore((...a) => ({ - ...createSelectedSecretStore(...a) - })); - } - - useEffect(() => { - const onRouteChangeStart = () => { - const state = storeRef.current?.getState(); - state?.action.reset(); - }; - - router.events.on("routeChangeStart", onRouteChangeStart); - return () => { - router.events.off("routeChangeStart", onRouteChangeStart); - }; - }, []); - - return {children}; -}; - -const useStoreContext = (selector: (state: SelectedEntriesState) => T): T => { - const ctx = useContext(StoreContext); - if (!ctx) throw new Error("Missing context provider"); - return useStore(ctx, selector); -}; - -export const useSelectedEntries = () => useStoreContext((state) => state.selectedEntries); -export const useSelectedEntryActions = () => useStoreContext((state) => state.action); diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index cd00af883..fab7ebecf 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -71,7 +71,11 @@ import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynam import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow"; import { SecretOverviewTableRow } from "./components/SecretOverviewTableRow"; import { SelectionPanel } from "./components/SelectionPanel/SelectionPanel"; -import { EntryType, useSelectedEntries, useSelectedEntryActions } from "./SecretOverviewPage.store"; + +export enum EntryType { + FOLDER = "folder", + SECRET = "secret" +} export const SecretOverviewPage = () => { const { t } = useTranslation(); @@ -84,15 +88,6 @@ export const SecretOverviewPage = () => { const [expandableTableWidth, setExpandableTableWidth] = useState(0); const [sortDir, setSortDir] = useState<"asc" | "desc">("asc"); - useEffect(() => { - const handleParentTableWidthResize = () => { - setExpandableTableWidth(parentTableRef.current?.clientWidth || 0); - }; - - window.addEventListener("resize", handleParentTableWidthResize); - return () => window.removeEventListener("resize", handleParentTableWidthResize); - }, []); - useEffect(() => { if (parentTableRef.current) { setExpandableTableWidth(parentTableRef.current.clientWidth); @@ -107,8 +102,55 @@ export const SecretOverviewPage = () => { const [searchFilter, setSearchFilter] = useState(""); const secretPath = (router.query?.secretPath as string) || "/"; - const selectedEntries = useSelectedEntries(); - const { toggle: toggleSelectedEntry } = useSelectedEntryActions(); + const [selectedEntries, setSelectedEntries] = useState<{ + [EntryType.FOLDER]: Record; + [EntryType.SECRET]: Record; + }>({ + [EntryType.FOLDER]: {}, + [EntryType.SECRET]: {} + }); + + const toggleSelectedEntry = useCallback( + (type: EntryType, key: string) => { + const isChecked = Boolean(selectedEntries[type]?.[key]); + const newChecks = { ...selectedEntries }; + + // remove selection if its present else add it + if (isChecked) { + delete newChecks[type][key]; + } else { + newChecks[type][key] = true; + } + + setSelectedEntries(newChecks); + }, + [selectedEntries] + ); + + const resetSelectedEntries = useCallback(() => { + setSelectedEntries({ + [EntryType.FOLDER]: {}, + [EntryType.SECRET]: {} + }); + }, []); + + useEffect(() => { + const handleParentTableWidthResize = () => { + setExpandableTableWidth(parentTableRef.current?.clientWidth || 0); + }; + + const onRouteChangeStart = () => { + resetSelectedEntries(); + }; + + router.events.on("routeChangeStart", onRouteChangeStart); + + window.addEventListener("resize", handleParentTableWidthResize); + return () => { + window.removeEventListener("resize", handleParentTableWidthResize); + router.events.off("routeChangeStart", onRouteChangeStart); + }; + }, []); useEffect(() => { if (!isWorkspaceLoading && !workspaceId && router.isReady) { @@ -553,6 +595,8 @@ export const SecretOverviewPage = () => { secretPath={secretPath} getSecretByKey={getSecretByKey} getFolderByNameAndEnv={getFolderByNameAndEnv} + selectedEntries={selectedEntries} + resetSelectedEntries={resetSelectedEntries} />
diff --git a/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx b/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx index 89fcef58c..6a2fe9bf6 100644 --- a/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SelectionPanel/SelectionPanel.tsx @@ -15,23 +15,35 @@ import { usePopUp } from "@app/hooks"; import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api"; import { DecryptedSecret, TDeleteSecretBatchDTO, TSecretFolder } from "@app/hooks/api/types"; -import { useSelectedEntries, useSelectedEntryActions } from "../../SecretOverviewPage.store"; +export enum EntryType { + FOLDER = "folder", + SECRET = "secret" +} type Props = { secretPath: string; getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; getFolderByNameAndEnv: (name: string, env: string) => TSecretFolder | undefined; + resetSelectedEntries: () => void; + selectedEntries: { + [EntryType.FOLDER]: Record; + [EntryType.SECRET]: Record; + }; }; -export const SelectionPanel = ({ getFolderByNameAndEnv, getSecretByKey, secretPath }: Props) => { +export const SelectionPanel = ({ + getFolderByNameAndEnv, + getSecretByKey, + secretPath, + resetSelectedEntries, + selectedEntries +}: Props) => { const { permission } = useProjectPermission(); const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "bulkDeleteEntries" ] as const); - const selectedEntries = useSelectedEntries(); - const { reset: resetSelectedEntries } = useSelectedEntryActions(); const selectedCount = Object.keys(selectedEntries.folder).length + Object.keys(selectedEntries.secret).length;