Disabled submit button when renaming all keys if key name is empty

This commit is contained in:
Rhythm Bhiwani
2024-03-03 02:49:35 +05:30
parent 3fa529dcb0
commit d8860e1ce3
2 changed files with 114 additions and 56 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "npm-proj-1709146141702-0.772936286416932EMIzNi", "name": "frontend",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
@@ -1,9 +1,12 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { faCheck, faClose } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faClose, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { z } from "zod";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { IconButton, Input, Spinner, Tooltip } from "@app/components/v2"; import { IconButton, Input, Spinner, Tooltip } from "@app/components/v2";
@@ -13,6 +16,7 @@ import {
useProjectPermission, useProjectPermission,
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { useToggle } from "@app/hooks";
import { useGetUserWsKey, useUpdateSecretV3 } from "@app/hooks/api"; import { useGetUserWsKey, useUpdateSecretV3 } from "@app/hooks/api";
import { DecryptedSecret } from "@app/hooks/api/types"; import { DecryptedSecret } from "@app/hooks/api/types";
import { SecretActionType } from "@app/views/SecretMainPage/components/SecretListView/SecretListView.utils"; import { SecretActionType } from "@app/views/SecretMainPage/components/SecretListView/SecretListView.utils";
@@ -24,7 +28,11 @@ type Props = {
getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined;
}; };
type Form = { key: string }; export const formSchema = z.object({
key: z.string().trim().min(1, { message: "Secret key is required" })
});
type TFormSchema = z.infer<typeof formSchema>;
function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }: Props) { function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }: Props) {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@@ -59,19 +67,32 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
const { data: decryptFileKey } = useGetUserWsKey(workspaceId); const { data: decryptFileKey } = useGetUserWsKey(workspaceId);
const [isSecNameCopied, setIsSecNameCopied] = useToggle(false);
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
const { const {
handleSubmit, handleSubmit,
control, control,
reset, reset,
formState: { isDirty, isSubmitting } trigger,
} = useForm<Form>({ getValues,
formState: { isDirty, isSubmitting, errors }
} = useForm<TFormSchema>({
defaultValues: { key: secretKey }, defaultValues: { key: secretKey },
values: { key: secretKey } values: { key: secretKey },
resolver: zodResolver(formSchema)
}); });
const handleFormSubmit = async (data: Form) => { useEffect(() => {
let timer: NodeJS.Timeout;
if (isSecNameCopied) {
timer = setTimeout(() => setIsSecNameCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isSecNameCopied]);
const handleFormSubmit = async (data: TFormSchema) => {
if (!data.key) { if (!data.key) {
createNotification({ createNotification({
type: "error", type: "error",
@@ -118,6 +139,12 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
}); });
}; };
const copyTokenToClipboard = () => {
const [key] = getValues(["key"]);
navigator.clipboard.writeText(key as string);
setIsSecNameCopied.on();
};
return ( return (
<form <form
onSubmit={handleSubmit(handleFormSubmit)} onSubmit={handleSubmit(handleFormSubmit)}
@@ -131,32 +158,57 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
<Controller <Controller
name="key" name="key"
control={control} control={control}
render={({ field }) => ( render={({ field, fieldState: { error } }) => (
<Input <Input
autoComplete="off" autoComplete="off"
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
autoCapitalization={currentWorkspace?.autoCapitalization} autoCapitalization={currentWorkspace?.autoCapitalization}
variant="plain" variant="plain"
isDisabled={isOverriden} isDisabled={isOverriden}
placeholder={error?.message}
onKeyUp={() => trigger("key")}
isError={Boolean(error)}
{...field} {...field}
className="w-full px-2 focus:text-bunker-100 focus:ring-transparent" className="w-full px-2 placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent"
/> />
)} )}
/> />
</div> </div>
{(isReadOnly || isOverriden) && (
{isReadOnly || isOverriden ? (
<span className="mr-5 rounded-md bg-mineshaft-500 px-2">Read Only</span> <span className="mr-5 rounded-md bg-mineshaft-500 px-2">Read Only</span>
)} ) : (
<div className="group flex w-20 items-center justify-center border-l border-mineshaft-500 py-1">
<AnimatePresence exitBeforeEnter> <AnimatePresence exitBeforeEnter>
{isDirty && ( {!isDirty ? (
<motion.div
key="options"
className="flex flex-shrink-0 items-center space-x-4 px-3"
initial={{ x: 0, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 10, opacity: 0 }}
>
<Tooltip content="Copy secret">
<IconButton
ariaLabel="copy-value"
variant="plain"
size="sm"
className="p-0 opacity-0 group-hover:opacity-100"
onClick={copyTokenToClipboard}
>
<FontAwesomeIcon icon={isSecNameCopied ? faCheck : faCopy} />
</IconButton>
</Tooltip>
</motion.div>
) : (
<motion.div <motion.div
key="options-save" key="options-save"
className="flex h-10 flex-shrink-0 items-center space-x-4 px-3" className="flex flex-shrink-0 items-center space-x-4 px-3"
initial={{ x: -10, opacity: 0 }} initial={{ x: -10, opacity: 0 }}
animate={{ x: 0, opacity: 1 }} animate={{ x: 0, opacity: 1 }}
exit={{ x: -10, opacity: 0 }} exit={{ x: -10, opacity: 0 }}
> >
<Tooltip content="Save"> <Tooltip content={errors.key ? errors.key.message : "Save"}>
<IconButton <IconButton
ariaLabel="more" ariaLabel="more"
variant="plain" variant="plain"
@@ -166,12 +218,16 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
"p-0 text-primary opacity-0 group-hover:opacity-100", "p-0 text-primary opacity-0 group-hover:opacity-100",
isDirty && "opacity-100" isDirty && "opacity-100"
)} )}
isDisabled={isSubmitting} isDisabled={isSubmitting || Boolean(errors.key)}
> >
{isSubmitting ? ( {isSubmitting ? (
<Spinner className="m-0 h-4 w-4 p-0" /> <Spinner className="m-0 h-4 w-4 p-0" />
) : ( ) : (
<FontAwesomeIcon icon={faCheck} size="lg" className="text-primary" /> <FontAwesomeIcon
icon={faCheck}
size="lg"
className={twMerge("text-primary", errors.key && "text-mineshaft-400")}
/>
)} )}
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@@ -193,6 +249,8 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
</div>
)}
</form> </form>
); );
} }