diff --git a/frontend/src/components/v2/Modal/Modal.tsx b/frontend/src/components/v2/Modal/Modal.tsx index 97e1f3d3c..ae1e7fd4a 100644 --- a/frontend/src/components/v2/Modal/Modal.tsx +++ b/frontend/src/components/v2/Modal/Modal.tsx @@ -29,7 +29,7 @@ export const ModalContent = forwardRef( diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.store.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.store.tsx index 99f4e135f..a92960b31 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.store.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.store.tsx @@ -28,13 +28,39 @@ const createSelectedSecretStore: StateCreator = (set) => ({ } }); -const StoreContext = createContext | null>(null); +export enum PopUpNames { + CreateSecretForm = "create-secret-form" +} +type PopUpState = { + popUp: Record; + popUpActions: { + togglePopUp: (id: PopUpNames, isOpen?: boolean) => void; + closePopUp: (id: PopUpNames) => void; + openPopUp: (id: PopUpNames, data?: any) => void; + }; +}; +const createPopUpStore: StateCreator = (set) => ({ + popUp: {}, + popUpActions: { + closePopUp: (id) => set((state) => ({ popUp: { ...state.popUp, [id]: { isOpen: false } } })), + openPopUp: (id, data) => + set((state) => ({ popUp: { ...state.popUp, [id]: { isOpen: true, data } } })), + togglePopUp: (id, isOpen) => + set((state) => ({ + popUp: { ...state.popUp, [id]: { isOpen: isOpen ?? !state.popUp[id].isOpen } } + })) + } +}); + +type CombinedState = SelectedSecretState & PopUpState; +const StoreContext = createContext | null>(null); export const StoreProvider = ({ children }: { children: ReactNode }) => { - const storeRef = useRef>(); + const storeRef = useRef>(); const router = useRouter(); if (!storeRef.current) { - storeRef.current = createStore((...a) => ({ - ...createSelectedSecretStore(...a) + storeRef.current = createStore((...a) => ({ + ...createSelectedSecretStore(...a), + ...createPopUpStore(...a) })); } @@ -53,7 +79,7 @@ export const StoreProvider = ({ children }: { children: ReactNode }) => { return {children}; }; -const useStoreContext = (selector: (state: SelectedSecretState) => T): T => { +const useStoreContext = (selector: (state: CombinedState) => T): T => { const ctx = useContext(StoreContext); if (!ctx) throw new Error("Missing "); return useStore(ctx, selector); @@ -62,3 +88,8 @@ const useStoreContext = (selector: (state: SelectedSecretStat // selected secret context export const useSelectedSecrets = () => useStoreContext((state) => state.selectedSecret); export const useSelectedSecretActions = () => useStoreContext((state) => state.action); + +// popup context +export const usePopUpState = (id: PopUpNames) => + useStoreContext((state) => state.popUp?.[id] || { isOpen: false }); +export const usePopUpAction = () => useStoreContext((state) => state.popUpActions); diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.tsx index e41543990..60fe31dce 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.tsx @@ -4,7 +4,6 @@ import { useRouter } from "next/router"; import { subject } from "@casl/ability"; import { faArrowDown, faArrowUp } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; import NavHeader from "@app/components/navigation/NavHeader"; import { PermissionDeniedBanner } from "@app/components/permissions"; @@ -28,6 +27,7 @@ import { } from "@app/hooks/api"; import { ActionBar } from "./components/ActionBar"; +import { CreateSecretForm } from "./components/CreateSecretForm"; import { FolderListView } from "./components/FolderListView"; import { PitDrawer } from "./components/PitDrawer"; import { SecretDropzone } from "./components/SecretDropzone"; @@ -218,7 +218,6 @@ export const SecretMainPage = () => { workspaceId={workspaceId} secretPath={secretPath} isVisible={isVisible} - decryptFileKey={decryptFileKey!} filter={filter} tags={tags} onVisiblilityToggle={handleToggleVisibility} @@ -226,15 +225,10 @@ export const SecretMainPage = () => { onSearchChange={handleSearchChange} onToggleTagFilter={handleTagToggle} snapshotCount={snapshotCount || 0} - autoCapitalization={currentWorkspace?.autoCapitalization} isSnapshotCountLoading={isSnapshotCountLoading} onClickRollbackMode={() => handlePopUpToggle("snapshots", true)} /> -
+
{isNotEmtpy && (
@@ -292,6 +286,13 @@ export const SecretMainPage = () => { {!canReadSecret && folders?.length === 0 && }
+ { isSmaller={isNotEmtpy} environments={currentWorkspace?.environments} /> + handlePopUpToggle("snapshots", isOpen)} + hasNextPage={hasNextSnapshotListPage} + fetchNextPage={fetchNextSnapshotList} + onSelectSnapshot={handleSelectSnapshot} + isFetchingNextPage={isFetchingNextSnapshotList} + /> ) : ( { onClickListSnapshot={() => handlePopUpToggle("snapshots", true)} /> )} - handlePopUpToggle("snapshots", isOpen)} - hasNextPage={hasNextSnapshotListPage} - fetchNextPage={fetchNextSnapshotList} - onSelectSnapshot={handleSelectSnapshot} - isFetchingNextPage={isFetchingNextSnapshotList} - />
); diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx index 5240c242f..0c3f3b03e 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/ActionBar.tsx @@ -43,11 +43,15 @@ import { import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useCreateFolder, useDeleteSecretBatch } from "@app/hooks/api"; -import { DecryptedSecret, TImportedSecrets, UserWsKeyPair, WsTag } from "@app/hooks/api/types"; +import { DecryptedSecret, TImportedSecrets, WsTag } from "@app/hooks/api/types"; -import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store"; +import { + PopUpNames, + usePopUpAction, + useSelectedSecretActions, + useSelectedSecrets +} from "../../SecretMainPage.store"; import { Filter, GroupBy } from "../../SecretMainPage.types"; -import { CreateSecretForm } from "./CreateSecretForm"; import { CreateSecretImportForm } from "./CreateSecretImportForm"; import { FolderForm } from "./FolderForm"; @@ -58,13 +62,11 @@ type Props = { environment: string; workspaceId: string; secretPath?: string; - decryptFileKey: UserWsKeyPair; filter: Filter; tags?: WsTag[]; isVisible?: boolean; snapshotCount: number; isSnapshotCountLoading?: boolean; - autoCapitalization?: boolean; onGroupByChange: (opt?: GroupBy) => void; onSearchChange: (term: string) => void; onToggleTagFilter: (tagId: string) => void; @@ -77,14 +79,12 @@ export const ActionBar = ({ importedSecrets = [], environment, workspaceId, - decryptFileKey, secretPath = "/", filter, tags = [], isVisible, snapshotCount, isSnapshotCountLoading, - autoCapitalization, onSearchChange, onToggleTagFilter, onGroupByChange, @@ -92,7 +92,6 @@ export const ActionBar = ({ onClickRollbackMode }: Props) => { const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ - "addSecret", "addFolder", "addSecretImport", "bulkDeleteSecrets", @@ -101,6 +100,7 @@ export const ActionBar = ({ ] as const); const { subscription } = useSubscription(); const { createNotification } = useNotificationContext(); + const { openPopUp } = usePopUpAction(); const { mutateAsync: createFolder } = useCreateFolder(); const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch(); @@ -235,7 +235,10 @@ export const ActionBar = ({ Apply tags to filter secrets {tags.map(({ _id, name, tagColor }) => ( onToggleTagFilter(_id)} + onClick={(evt) => { + evt.preventDefault(); + onToggleTagFilter(_id); + }} key={_id} icon={filter?.tags[_id] && } iconPos="right" @@ -300,7 +303,7 @@ export const ActionBar = ({
{/* all the side triggers from actions like modals etc */} - handlePopUpClose("addSecret")} - onTogglePopUp={(isOpen) => handlePopUpToggle("addSecret", isOpen)} - /> ; type Props = { - secrets?: DecryptedSecret[]; environment: string; workspaceId: string; decryptFileKey: UserWsKeyPair; secretPath?: string; // modal props - isOpen?: boolean; - onClose: () => void; - onTogglePopUp: (isOpen: boolean) => void; autoCapitalize?: boolean; }; @@ -32,9 +30,6 @@ export const CreateSecretForm = ({ workspaceId, decryptFileKey, secretPath = "/", - isOpen, - onClose, - onTogglePopUp, autoCapitalize = true }: Props) => { const { @@ -44,6 +39,8 @@ export const CreateSecretForm = ({ reset, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(typeSchema) }); + const { isOpen } = usePopUpState(PopUpNames.CreateSecretForm); + const { closePopUp, togglePopUp } = usePopUpAction(); const { createNotification } = useNotificationContext(); @@ -61,7 +58,7 @@ export const CreateSecretForm = ({ type: "shared", latestFileKey: decryptFileKey }); - onClose(); + closePopUp(PopUpNames.CreateSecretForm); reset(); createNotification({ type: "success", @@ -77,7 +74,10 @@ export const CreateSecretForm = ({ }; return ( - + togglePopUp(PopUpNames.CreateSecretForm, state)} + > @@ -264,7 +266,7 @@ export const CopySecretsFromBoard = ({ Include secret values -
+
-
diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx index aeb97e772..838a5f0fb 100644 --- a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx @@ -17,6 +17,7 @@ import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api"; import { secretKeys } from "@app/hooks/api/secrets/queries"; import { DecryptedSecret, UserWsKeyPair } from "@app/hooks/api/types"; +import { PopUpNames, usePopUpAction } from "../../SecretMainPage.store"; import { CopySecretsFromBoard } from "./CopySecretsFromBoard"; const parseJson = (src: ArrayBuffer) => { @@ -62,6 +63,7 @@ export const SecretDropzone = ({ "overlapKeyWarning" ] as const); const queryClient = useQueryClient(); + const { openPopUp } = usePopUpAction(); const { mutateAsync: updateSecretBatch, isLoading: isUpdatingSecrets } = useUpdateSecretBatch({ options: { onSuccess: undefined } @@ -274,6 +276,7 @@ export const SecretDropzone = ({ workspaceId={workspaceId} decryptFileKey={decryptFileKey} secretPath={secretPath} + isSmaller={isSmaller} /> {!isSmaller && ( {(isAllowed) => ( - )} diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx index fb98bef98..050480456 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx @@ -207,7 +207,7 @@ export const SecretListView = ({ // personal secret change if (overrideAction === "deleted") { await handleSecretOperation("delete", "personal", key); - } else if (overrideAction && idOverride){ + } else if (overrideAction && idOverride) { await handleSecretOperation("update", "personal", oldKey, { value: valueOverride, newKey: hasKeyChanged ? key : undefined, @@ -239,7 +239,7 @@ export const SecretListView = ({ }); } }, - [] + [environment, secretPath] ); const handleSecretDelete = useCallback(async () => { @@ -268,7 +268,7 @@ export const SecretListView = ({ text: "Failed to delete secret" }); } - }, [(popUp.deleteSecret?.data as DecryptedSecret)?.key]); + }, [(popUp.deleteSecret?.data as DecryptedSecret)?.key, environment, secretPath]); // for optimization on minimise re-rendering of secret items const onCreateTag = useCallback(() => handlePopUpOpen("createTag"), []);