From 5142e6e5f68529a9045d92849fee6877f9d2549b Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 26 Apr 2024 16:07:27 +0800 Subject: [PATCH 1/6] feature: created secret path input component with autocomplete support --- .../v2/SecretPathInput/SecretPathInput.tsx | 175 ++++++++++++++++++ .../components/v2/SecretPathInput/index.tsx | 1 + 2 files changed, 176 insertions(+) create mode 100644 frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx create mode 100644 frontend/src/components/v2/SecretPathInput/index.tsx diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx new file mode 100644 index 000000000..115ff488c --- /dev/null +++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx @@ -0,0 +1,175 @@ +import { InputHTMLAttributes, useEffect, useRef, useState } from "react"; +import { faFolder } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import * as Popover from "@radix-ui/react-popover"; +import { twMerge } from "tailwind-merge"; + +import { useWorkspace } from "@app/context"; +import { useDebounce } from "@app/hooks"; +import { useGetFoldersByEnv } from "@app/hooks/api"; + +import { Input } from "../Input"; + +type Props = Omit, "size"> & { + value?: string | null; + isImport?: boolean; + isVisible?: boolean; + isReadOnly?: boolean; + isDisabled?: boolean; + environment?: string; + containerClassName?: string; +}; + +export const SecretPathInput = ({ + containerClassName, + onChange, + environment, + value: propValue, + ...props +}: Props) => { + const [inputValue, setInputValue] = useState(propValue ?? ""); + const [secretPath, setSecretPath] = useState("/"); + const [suggestions, setSuggestions] = useState([]); + const [highlightedIndex, setHighlightedIndex] = useState(-1); + const inputRef = useRef(null); + const debouncedInputValue = useDebounce(inputValue, 200); + + const { currentWorkspace } = useWorkspace(); + const workspaceId = currentWorkspace?.id || ""; + const { folderNames: folders } = useGetFoldersByEnv({ + path: secretPath, + environments: [environment || currentWorkspace?.environments?.[0].slug!], + projectId: workspaceId + }); + + useEffect(() => { + setInputValue(propValue ?? ""); + }, [propValue]); + + useEffect(() => { + setInputValue("/"); + setSecretPath("/"); + }, [environment]); + + useEffect(() => { + // update secret path if input is valid + if ( + debouncedInputValue.length > 0 && + debouncedInputValue[debouncedInputValue.length - 1] === "/" + ) { + setSecretPath(debouncedInputValue); + } + + // filter suggestions based on matching + const searchFragment = debouncedInputValue.split("/").pop() || ""; + + const filteredSuggestions = folders + .filter((suggestionEntry) => + suggestionEntry.toUpperCase().startsWith(searchFragment.toUpperCase()) + ) + .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); + + setSuggestions(filteredSuggestions); + }, [debouncedInputValue]); + + const handleSuggestionSelect = (selectedIndex: number) => { + if (!suggestions[selectedIndex]) { + return; + } + + const validPaths = inputValue.split("/"); + validPaths.pop(); + + const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`; + onChange?.({ target: { value: newValue } } as any); + setInputValue(newValue); + setSecretPath(newValue); + setHighlightedIndex(-1); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + const mod = (n: number, m: number) => ((n % m) + m) % m; + if (e.key === "ArrowDown") { + setHighlightedIndex((prevIndex) => mod(prevIndex + 1, folders.length)); + } else if (e.key === "ArrowUp") { + setHighlightedIndex((prevIndex) => mod(prevIndex - 1, folders.length)); + } else if (e.key === "Enter" && highlightedIndex >= 0) { + handleSuggestionSelect(highlightedIndex); + } + if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) { + e.preventDefault(); + } + }; + + const handleInputChange = (e: any) => { + // propagate event to react-hook-form onChange + if (onChange) { + onChange(e); + } + + setInputValue(e.target.value); + }; + + return ( + 0 && inputValue.length > 1} + onOpenChange={() => { + setHighlightedIndex(-1); + }} + > + + + + e.preventDefault()} + className={twMerge( + "relative top-2 z-[100] overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 font-inter text-bunker-100 shadow-md" + )} + style={{ + width: "var(--radix-popover-trigger-width)", + maxHeight: "var(--radix-select-content-available-height)" + }} + > +
+ {suggestions.map((suggestion, i) => ( +
{ + e.preventDefault(); + setHighlightedIndex(i); + handleSuggestionSelect(i); + }} + style={{ pointerEvents: "auto" }} + className="flex items-center justify-between border-mineshaft-600 text-left" + key={`secret-reference-secret-${i + 1}`} + > +
+
+
+ +
+
{suggestion}
+
+
+
+ ))} +
+
+
+ ); +}; diff --git a/frontend/src/components/v2/SecretPathInput/index.tsx b/frontend/src/components/v2/SecretPathInput/index.tsx new file mode 100644 index 000000000..5d8595891 --- /dev/null +++ b/frontend/src/components/v2/SecretPathInput/index.tsx @@ -0,0 +1 @@ +export { SecretPathInput } from "./SecretPathInput"; From 656ec4bf169110efaa4302bf31fe9fee0cee34c8 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 26 Apr 2024 18:18:23 +0800 Subject: [PATCH 2/6] feature: migrated path inputs to new component --- .../v2/SecretPathInput/SecretPathInput.tsx | 17 ++++++++++++----- .../integrations/cloudflare-pages/create.tsx | 14 ++++---------- .../integrations/cloudflare-workers/create.tsx | 15 ++++----------- .../integrations/gcp-secret-manager/create.tsx | 7 ++++++- .../src/pages/integrations/gitlab/create.tsx | 7 ++++++- .../pages/integrations/hasura-cloud/create.tsx | 16 ++++++---------- .../src/pages/integrations/heroku/create.tsx | 17 +++++++---------- .../src/pages/integrations/render/create.tsx | 8 ++++++-- .../SpecificPrivilegeSection.tsx | 9 ++++++++- .../MemberRoleForm/SpecificPrivilegeSection.tsx | 9 ++++++++- .../components/SecretPolicyForm.tsx | 10 ++++++++-- .../ActionBar/CreateSecretImportForm.tsx | 17 +++++------------ .../SecretDropzone/CopySecretsFromBoard.tsx | 7 ++++++- .../steps/RotationOutputForm.tsx | 3 ++- 14 files changed, 88 insertions(+), 68 deletions(-) diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx index 115ff488c..3a097f29d 100644 --- a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx +++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx @@ -43,19 +43,26 @@ export const SecretPathInput = ({ }); useEffect(() => { - setInputValue(propValue ?? ""); + const initialValue = propValue ?? "/"; + setInputValue(initialValue); + setSecretPath(initialValue); + onChange?.({ target: { value: initialValue } } as any); }, [propValue]); useEffect(() => { - setInputValue("/"); - setSecretPath("/"); + if (environment) { + setInputValue("/"); + setSecretPath("/"); + onChange?.({ target: { value: "/" } } as any); + } }, [environment]); useEffect(() => { // update secret path if input is valid if ( - debouncedInputValue.length > 0 && - debouncedInputValue[debouncedInputValue.length - 1] === "/" + (debouncedInputValue.length > 0 && + debouncedInputValue[debouncedInputValue.length - 1] === "/") || + debouncedInputValue.length === 0 ) { setSecretPath(debouncedInputValue); } diff --git a/frontend/src/pages/integrations/cloudflare-pages/create.tsx b/frontend/src/pages/integrations/cloudflare-pages/create.tsx index c2eb1c61a..1b2835b76 100644 --- a/frontend/src/pages/integrations/cloudflare-pages/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-pages/create.tsx @@ -4,16 +4,10 @@ import axios from "axios"; import queryString from "query-string"; import { createNotification } from "@app/components/notifications"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem} from "../../../components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById @@ -27,7 +21,6 @@ const cloudflareEnvironments = [ export default function CloudflarePagesIntegrationPage() { const router = useRouter(); const { mutateAsync } = useCreateIntegration(); - const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); const [secretPath, setSecretPath] = useState("/"); @@ -130,9 +123,10 @@ export default function CloudflarePagesIntegrationPage() { - setSecretPath(evt.target.value)} + environment={selectedSourceEnvironment} placeholder="Provide a path, default is /" /> diff --git a/frontend/src/pages/integrations/cloudflare-workers/create.tsx b/frontend/src/pages/integrations/cloudflare-workers/create.tsx index 43808e98d..c706cdd5d 100644 --- a/frontend/src/pages/integrations/cloudflare-workers/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-workers/create.tsx @@ -4,17 +4,10 @@ import axios from "axios"; import queryString from "query-string"; import { createNotification } from "@app/components/notifications"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem -} from "../../../components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById @@ -23,7 +16,6 @@ import { export default function CloudflareWorkersIntegrationPage() { const router = useRouter(); const { mutateAsync } = useCreateIntegration(); - const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); @@ -122,9 +114,10 @@ export default function CloudflareWorkersIntegrationPage() { - setSecretPath(evt.target.value)} + environment={selectedSourceEnvironment} placeholder="Provide a path, default is /" /> diff --git a/frontend/src/pages/integrations/gcp-secret-manager/create.tsx b/frontend/src/pages/integrations/gcp-secret-manager/create.tsx index 497f02370..12dcb6ca8 100644 --- a/frontend/src/pages/integrations/gcp-secret-manager/create.tsx +++ b/frontend/src/pages/integrations/gcp-secret-manager/create.tsx @@ -11,6 +11,7 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { usePopUp } from "@app/hooks"; import { useCreateIntegration } from "@app/hooks/api"; @@ -258,7 +259,11 @@ export default function GCPSecretManagerCreateIntegrationPage() { isError={Boolean(error)} errorText={error?.message} > - + )} /> diff --git a/frontend/src/pages/integrations/gitlab/create.tsx b/frontend/src/pages/integrations/gitlab/create.tsx index 183040341..15daad270 100644 --- a/frontend/src/pages/integrations/gitlab/create.tsx +++ b/frontend/src/pages/integrations/gitlab/create.tsx @@ -11,6 +11,7 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { usePopUp } from "@app/hooks"; import { useCreateIntegration } from "@app/hooks/api"; @@ -268,7 +269,11 @@ export default function GitLabCreateIntegrationPage() { isError={Boolean(error)} errorText={error?.message} > - + )} /> diff --git a/frontend/src/pages/integrations/hasura-cloud/create.tsx b/frontend/src/pages/integrations/hasura-cloud/create.tsx index 6f8eac255..476a6a8f5 100644 --- a/frontend/src/pages/integrations/hasura-cloud/create.tsx +++ b/frontend/src/pages/integrations/hasura-cloud/create.tsx @@ -9,15 +9,8 @@ import { yupResolver } from "@hookform/resolvers/yup"; import queryString from "query-string"; import * as yup from "yup"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem -} from "@app/components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -38,6 +31,7 @@ export default function HasuraCloudCreateIntegrationPage() { const { control, handleSubmit, + watch, formState: { isSubmitting } } = useForm({ resolver: yupResolver(schema) @@ -51,6 +45,8 @@ export default function HasuraCloudCreateIntegrationPage() { (integrationAuthId as string) ?? "" ); + const selectedSourceEnvironment = watch("sourceEnvironment"); + const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "" @@ -147,7 +143,7 @@ export default function HasuraCloudCreateIntegrationPage() { name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/pages/integrations/heroku/create.tsx b/frontend/src/pages/integrations/heroku/create.tsx index db2237253..4d25b7260 100644 --- a/frontend/src/pages/integrations/heroku/create.tsx +++ b/frontend/src/pages/integrations/heroku/create.tsx @@ -17,19 +17,12 @@ import queryString from "query-string"; // import { App, Pipeline } from "@app/hooks/api/integrationAuth/types"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; // import { RadioGroup } from "@app/components/v2/RadioGroup"; import { useCreateIntegration } from "@app/hooks/api"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem -} from "../../../components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById @@ -280,7 +273,11 @@ export default function HerokuCreateIntegrationPage() { name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/pages/integrations/render/create.tsx b/frontend/src/pages/integrations/render/create.tsx index d911940ac..e52f3668e 100644 --- a/frontend/src/pages/integrations/render/create.tsx +++ b/frontend/src/pages/integrations/render/create.tsx @@ -15,6 +15,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import queryString from "query-string"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration } from "@app/hooks/api"; import { @@ -22,7 +23,6 @@ import { Card, CardTitle, FormControl, - Input, Select, SelectItem, Switch @@ -185,7 +185,11 @@ export default function RenderCreateIntegrationPage() { name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx b/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx index 83a6b6e45..56248145b 100644 --- a/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx +++ b/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx @@ -34,6 +34,7 @@ import { Tag, Tooltip } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ProjectPermissionActions, ProjectPermissionSub, @@ -115,6 +116,7 @@ const SpecificPrivilegeSecretForm = ({ }); const temporaryAccessField = privilegeForm.watch("temporaryAccess"); + const selectedEnvironmentSlug = privilegeForm.watch("environmentSlug"); const isTemporary = temporaryAccessField?.isTemporary; const isExpired = temporaryAccessField.isTemporary && @@ -220,7 +222,12 @@ const SpecificPrivilegeSecretForm = ({ name="secretPath" render={({ field }) => ( - + )} /> diff --git a/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx b/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx index 2df959a94..97321de7c 100644 --- a/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx +++ b/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx @@ -34,6 +34,7 @@ import { Tag, Tooltip } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ProjectPermissionActions, ProjectPermissionSub, @@ -107,6 +108,7 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri }); const temporaryAccessField = privilegeForm.watch("temporaryAccess"); + const selectedEnvironmentSlug = privilegeForm.watch("environmentSlug"); const isTemporary = temporaryAccessField?.isTemporary; const isExpired = temporaryAccessField.isTemporary && @@ -208,7 +210,12 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri name="secretPath" render={({ field }) => ( - + )} /> diff --git a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx index eb9d5303a..185c596c2 100644 --- a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx +++ b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx @@ -20,6 +20,7 @@ import { Select, SelectItem } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; import { useCreateSecretApprovalPolicy, useUpdateSecretApprovalPolicy } from "@app/hooks/api"; import { TSecretApprovalPolicy } from "@app/hooks/api/types"; @@ -59,13 +60,14 @@ export const SecretPolicyForm = ({ control, handleSubmit, reset, + watch, formState: { isSubmitting } } = useForm({ resolver: zodResolver(formSchema), values: editValues ? { ...editValues, environment: editValues.environment.slug } : undefined }); const { currentWorkspace } = useWorkspace(); - + const selectedEnvironment = watch("environment"); const environments = currentWorkspace?.environments || []; useEffect(() => { @@ -174,7 +176,11 @@ export const SecretPolicyForm = ({ name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx index fa8340465..a6dfe7c0e 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx @@ -4,15 +4,8 @@ import { AxiosError } from "axios"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { - Button, - FormControl, - Input, - Modal, - ModalContent, - Select, - SelectItem -} from "@app/components/v2"; +import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; import { useCreateSecretImport } from "@app/hooks/api"; @@ -50,12 +43,12 @@ export const CreateSecretImportForm = ({ handleSubmit, control, reset, + watch, formState: { isSubmitting } } = useForm({ resolver: zodResolver(typeSchema) }); const { currentWorkspace } = useWorkspace(); const environments = currentWorkspace?.environments || []; - - + const selectedEnvironment = watch("environment"); const { mutateAsync: createSecretImport } = useCreateSecretImport(); @@ -130,7 +123,7 @@ export const CreateSecretImportForm = ({ defaultValue="/" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx index 574b8da94..b00bae421 100644 --- a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx @@ -28,6 +28,7 @@ import { Skeleton, Tooltip } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useDebounce } from "@app/hooks"; import { useGetProjectSecrets } from "@app/hooks/api"; @@ -193,7 +194,11 @@ export const CopySecretsFromBoard = ({ )} /> - +
diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx index 4b6449a33..3ddacf5b4 100644 --- a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, FormControl, Input, Select, SelectItem, Spinner } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; import { useGetProjectSecrets, useGetUserWsKey } from "@app/hooks/api"; @@ -78,7 +79,7 @@ export const RotationOutputForm = ({ onSubmit, onCancel, outputSchema = {} }: Pr defaultValue="/" render={({ field }) => ( - + )} /> From 5ff30aed102dc9c08b84e4875faee639c3e96293 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 26 Apr 2024 18:46:16 +0800 Subject: [PATCH 3/6] fix: addressed suggestion incomplete issue --- .../src/components/v2/SecretPathInput/SecretPathInput.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx index 3a097f29d..348da6a88 100644 --- a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx +++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx @@ -43,10 +43,7 @@ export const SecretPathInput = ({ }); useEffect(() => { - const initialValue = propValue ?? "/"; - setInputValue(initialValue); - setSecretPath(initialValue); - onChange?.({ target: { value: initialValue } } as any); + setInputValue(propValue ?? "/"); }, [propValue]); useEffect(() => { @@ -69,7 +66,6 @@ export const SecretPathInput = ({ // filter suggestions based on matching const searchFragment = debouncedInputValue.split("/").pop() || ""; - const filteredSuggestions = folders .filter((suggestionEntry) => suggestionEntry.toUpperCase().startsWith(searchFragment.toUpperCase()) From cc25639157ea3d349db7317d9b27b27c5bc7ce3d Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 26 Apr 2024 18:59:39 +0800 Subject: [PATCH 4/6] fix: resolved loop traversal of suggestions --- .../src/components/v2/SecretPathInput/SecretPathInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx index 348da6a88..fb110c2e7 100644 --- a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx +++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx @@ -93,9 +93,9 @@ export const SecretPathInput = ({ const handleKeyDown = (e: React.KeyboardEvent) => { const mod = (n: number, m: number) => ((n % m) + m) % m; if (e.key === "ArrowDown") { - setHighlightedIndex((prevIndex) => mod(prevIndex + 1, folders.length)); + setHighlightedIndex((prevIndex) => mod(prevIndex + 1, suggestions.length)); } else if (e.key === "ArrowUp") { - setHighlightedIndex((prevIndex) => mod(prevIndex - 1, folders.length)); + setHighlightedIndex((prevIndex) => mod(prevIndex - 1, suggestions.length)); } else if (e.key === "Enter" && highlightedIndex >= 0) { handleSuggestionSelect(highlightedIndex); } From 2b0670a4094daaed487acace43172adf80a5d770 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 26 Apr 2024 19:20:05 +0800 Subject: [PATCH 5/6] fix: addressed suggestion issue in copy secrets from board --- .../v2/SecretPathInput/SecretPathInput.tsx | 5 ++--- .../SecretDropzone/CopySecretsFromBoard.tsx | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx index fb110c2e7..1024a5c08 100644 --- a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx +++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx @@ -1,4 +1,4 @@ -import { InputHTMLAttributes, useEffect, useRef, useState } from "react"; +import { InputHTMLAttributes, useEffect, useState } from "react"; import { faFolder } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as Popover from "@radix-ui/react-popover"; @@ -31,7 +31,6 @@ export const SecretPathInput = ({ const [secretPath, setSecretPath] = useState("/"); const [suggestions, setSuggestions] = useState([]); const [highlightedIndex, setHighlightedIndex] = useState(-1); - const inputRef = useRef(null); const debouncedInputValue = useDebounce(inputValue, 200); const { currentWorkspace } = useWorkspace(); @@ -88,6 +87,7 @@ export const SecretPathInput = ({ setInputValue(newValue); setSecretPath(newValue); setHighlightedIndex(-1); + setSuggestions([]); }; const handleKeyDown = (e: React.KeyboardEvent) => { @@ -125,7 +125,6 @@ export const SecretPathInput = ({ {...props} type="text" autoComplete="off" - ref={inputRef} onKeyDown={handleKeyDown} value={inputValue} onChange={handleInputChange} diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx index b00bae421..1d83f591b 100644 --- a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx @@ -77,7 +77,6 @@ export const CopySecretsFromBoard = ({ handleSubmit, control, watch, - register, reset, setValue, formState: { isDirty } @@ -193,13 +192,19 @@ export const CopySecretsFromBoard = ({ )} /> - - - + ( + + + + )} + />
From d6a5bf9d501c67d5cb5b23351bff9d8cf0e8ad8e Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Sat, 27 Apr 2024 00:32:34 +0800 Subject: [PATCH 6/6] adjustment: simplified onchange propagation --- .../components/v2/SecretPathInput/SecretPathInput.tsx | 9 +++++---- .../src/pages/integrations/cloudflare-pages/create.tsx | 2 +- .../src/pages/integrations/cloudflare-workers/create.tsx | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx index 1024a5c08..1487cb302 100644 --- a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx +++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx @@ -10,7 +10,7 @@ import { useGetFoldersByEnv } from "@app/hooks/api"; import { Input } from "../Input"; -type Props = Omit, "size"> & { +type Props = Omit, "size" | "onChange"> & { value?: string | null; isImport?: boolean; isVisible?: boolean; @@ -18,6 +18,7 @@ type Props = Omit, "size"> & { isDisabled?: boolean; environment?: string; containerClassName?: string; + onChange?: (arg: string) => void; }; export const SecretPathInput = ({ @@ -49,7 +50,7 @@ export const SecretPathInput = ({ if (environment) { setInputValue("/"); setSecretPath("/"); - onChange?.({ target: { value: "/" } } as any); + onChange?.("/"); } }, [environment]); @@ -83,7 +84,7 @@ export const SecretPathInput = ({ validPaths.pop(); const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`; - onChange?.({ target: { value: newValue } } as any); + onChange?.(newValue); setInputValue(newValue); setSecretPath(newValue); setHighlightedIndex(-1); @@ -107,7 +108,7 @@ export const SecretPathInput = ({ const handleInputChange = (e: any) => { // propagate event to react-hook-form onChange if (onChange) { - onChange(e); + onChange(e.target.value); } setInputValue(e.target.value); diff --git a/frontend/src/pages/integrations/cloudflare-pages/create.tsx b/frontend/src/pages/integrations/cloudflare-pages/create.tsx index 1b2835b76..570b2b83a 100644 --- a/frontend/src/pages/integrations/cloudflare-pages/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-pages/create.tsx @@ -125,7 +125,7 @@ export default function CloudflarePagesIntegrationPage() { setSecretPath(evt.target.value)} + onChange={(value) => setSecretPath(value)} environment={selectedSourceEnvironment} placeholder="Provide a path, default is /" /> diff --git a/frontend/src/pages/integrations/cloudflare-workers/create.tsx b/frontend/src/pages/integrations/cloudflare-workers/create.tsx index c706cdd5d..f4a35d47c 100644 --- a/frontend/src/pages/integrations/cloudflare-workers/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-workers/create.tsx @@ -116,7 +116,7 @@ export default function CloudflareWorkersIntegrationPage() { setSecretPath(evt.target.value)} + onChange={(value) => setSecretPath(value)} environment={selectedSourceEnvironment} placeholder="Provide a path, default is /" />