the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Use react-hook-form and zod in add modals

Add @hookform/resolvers and zod-based validation to
AddFile/AddSecret/AddVariable/AddVolume
modals (useForm, zodResolver, register, handleSubmit, reset, show
errors). Refactor
useAddFile/useAddSecret/useAddVariable/useAddVolume hooks to accept a
payload object
(sandboxId, name/path, value/content) for mutationFn. Implement
DeleteSandboxModal
delete handler and update confirmation copy. Update dependencies (add
@hookform/resolvers).

+256 -89
+5
apps/web/bun.lock
··· 6 6 "name": "pocketenv", 7 7 "dependencies": { 8 8 "@cloudflare/sandbox": "^0.7.4", 9 + "@hookform/resolvers": "^5.2.2", 9 10 "@iconify/json": "^2.2.438", 10 11 "@iconify/tailwind4": "^1.2.1", 11 12 "@paralleldrive/cuid2": "^3.3.0", ··· 174 175 175 176 "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], 176 177 178 + "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="], 179 + 177 180 "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 178 181 179 182 "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], ··· 259 262 "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], 260 263 261 264 "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 265 + 266 + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], 262 267 263 268 "@swc/core": ["@swc/core@1.15.11", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.11", "@swc/core-darwin-x64": "1.15.11", "@swc/core-linux-arm-gnueabihf": "1.15.11", "@swc/core-linux-arm64-gnu": "1.15.11", "@swc/core-linux-arm64-musl": "1.15.11", "@swc/core-linux-x64-gnu": "1.15.11", "@swc/core-linux-x64-musl": "1.15.11", "@swc/core-win32-arm64-msvc": "1.15.11", "@swc/core-win32-ia32-msvc": "1.15.11", "@swc/core-win32-x64-msvc": "1.15.11" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w=="], 264 269
+1
apps/web/package.json
··· 11 11 }, 12 12 "dependencies": { 13 13 "@cloudflare/sandbox": "^0.7.4", 14 + "@hookform/resolvers": "^5.2.2", 14 15 "@iconify/json": "^2.2.438", 15 16 "@iconify/tailwind4": "^1.2.1", 16 17 "@paralleldrive/cuid2": "^3.3.0",
+51 -16
apps/web/src/components/contextmenu/AddEnvironmentVariableModal/AddEnvironmentVariableModal.tsx
··· 1 1 import { useEffect } from "react"; 2 2 import { createPortal } from "react-dom"; 3 + import { useForm } from "react-hook-form"; 4 + import { z } from "zod"; 5 + import { zodResolver } from "@hookform/resolvers/zod"; 3 6 import { useAddVariableMutation } from "../../../hooks/useVariable"; 4 7 8 + const schema = z.object({ 9 + name: z.string().min(1, "Name is required"), 10 + value: z.string().min(1, "Value is required"), 11 + }); 12 + 13 + type FormValues = z.infer<typeof schema>; 14 + 5 15 export type AddEnvironmentVariableModalProps = { 6 16 isOpen: boolean; 7 17 onClose: () => void; ··· 13 23 onClose, 14 24 sandboxId, 15 25 }: AddEnvironmentVariableModalProps) { 16 - const { mutateAsync } = useAddVariableMutation(sandboxId, "", ""); 26 + const { mutateAsync } = useAddVariableMutation(); 27 + const { 28 + register, 29 + handleSubmit, 30 + reset, 31 + formState: { errors }, 32 + } = useForm<FormValues>({ 33 + resolver: zodResolver(schema), 34 + }); 35 + 17 36 useEffect(() => { 18 37 const handleEscapeKey = (event: KeyboardEvent) => { 19 38 if (event.key === "Escape" && isOpen) { ··· 43 62 onClose(); 44 63 }; 45 64 46 - const onAddVariable = (e: React.MouseEvent<HTMLButtonElement>) => { 47 - e.stopPropagation(); 65 + const onSubmit = async (data: FormValues) => { 66 + await mutateAsync({ 67 + sandboxId, 68 + name: data.name, 69 + value: data.value, 70 + }); 71 + reset(); 48 72 onClose(); 49 73 }; 50 74 ··· 77 101 <span className="icon-[tabler--x] size-4"></span> 78 102 </button> 79 103 </div> 80 - <div className="modal-body "> 81 - <> 104 + <form onSubmit={handleSubmit(onSubmit)}> 105 + <div className="modal-body"> 82 106 <div className="form-control w-full"> 83 107 <label className="label"> 84 108 <span className="label-text font-bold mb-1 text-[14px]"> ··· 91 115 placeholder="YOUR_VARIABLE_NAME" 92 116 className="grow" 93 117 autoComplete="off" 94 - name="search-filter" 95 118 data-1p-ignore 96 119 data-lpignore="true" 97 120 data-form-type="other" 121 + {...register("name")} 98 122 /> 99 123 </div> 124 + {errors.name && ( 125 + <span className="text-error text-[12px] mt-1"> 126 + {errors.name.message} 127 + </span> 128 + )} 100 129 <div className="mt-5"> 101 130 <label className="label"> 102 131 <span className="label-text font-bold mb-1 text-[14px]"> ··· 107 136 className="textarea max-w-full h-[250px] text-[14px] font-semibold" 108 137 aria-label="Textarea" 109 138 placeholder="Variable Value" 139 + {...register("value")} 110 140 ></textarea> 141 + {errors.value && ( 142 + <span className="text-error text-[12px] mt-1 block"> 143 + {errors.value.message} 144 + </span> 145 + )} 111 146 </div> 112 147 </div> 113 - </> 114 - </div> 115 - <div className="modal-footer"> 116 - <button 117 - className="btn btn-primary w-35 font-semibold" 118 - onClick={onAddVariable} 119 - > 120 - Add Variable 121 - </button> 122 - </div> 148 + </div> 149 + <div className="modal-footer"> 150 + <button 151 + type="submit" 152 + className="btn btn-primary w-35 font-semibold" 153 + > 154 + Add Variable 155 + </button> 156 + </div> 157 + </form> 123 158 </div> 124 159 </div> 125 160 </div>
+51 -16
apps/web/src/components/contextmenu/AddFileModal/AddFileModal.tsx
··· 1 1 import { useEffect } from "react"; 2 2 import { createPortal } from "react-dom"; 3 + import { useForm } from "react-hook-form"; 4 + import { z } from "zod"; 5 + import { zodResolver } from "@hookform/resolvers/zod"; 3 6 import { useAddFileMutation } from "../../../hooks/useFile"; 4 7 8 + const schema = z.object({ 9 + path: z.string().min(1, "Path is required"), 10 + content: z.string().min(1, "Content is required"), 11 + }); 12 + 13 + type FormValues = z.infer<typeof schema>; 14 + 5 15 export type AddFileModalProps = { 6 16 isOpen: boolean; 7 17 onClose: () => void; ··· 9 19 }; 10 20 11 21 function AddFileModal({ isOpen, onClose, sandboxId }: AddFileModalProps) { 12 - const { mutateAsync } = useAddFileMutation(sandboxId, "", ""); 22 + const { mutateAsync } = useAddFileMutation(); 23 + const { 24 + register, 25 + handleSubmit, 26 + reset, 27 + formState: { errors }, 28 + } = useForm<FormValues>({ 29 + resolver: zodResolver(schema), 30 + }); 31 + 13 32 useEffect(() => { 14 33 const handleEscapeKey = (event: KeyboardEvent) => { 15 34 if (event.key === "Escape" && isOpen) { ··· 39 58 onClose(); 40 59 }; 41 60 42 - const onAddFile = (e: React.MouseEvent<HTMLButtonElement>) => { 43 - e.stopPropagation(); 61 + const onSubmit = async (data: FormValues) => { 62 + await mutateAsync({ 63 + sandboxId, 64 + path: data.path, 65 + content: data.content, 66 + }); 67 + reset(); 44 68 onClose(); 45 69 }; 46 70 ··· 73 97 <span className="icon-[tabler--x] size-4"></span> 74 98 </button> 75 99 </div> 76 - <div className="modal-body "> 77 - <> 100 + <form onSubmit={handleSubmit(onSubmit)}> 101 + <div className="modal-body"> 78 102 <label className="label"> 79 103 <span className="label-text font-bold mb-1 text-[14px]"> 80 104 Path ··· 86 110 placeholder="File Mount Path, e.g /root/.openclaw/openclaw.json" 87 111 className="grow" 88 112 autoComplete="off" 89 - name="search-filter" 90 113 data-1p-ignore 91 114 data-lpignore="true" 92 115 data-form-type="other" 116 + {...register("path")} 93 117 /> 94 118 </div> 119 + {errors.path && ( 120 + <span className="text-error text-[12px] mt-1"> 121 + {errors.path.message} 122 + </span> 123 + )} 95 124 <div className="mt-5"> 96 125 <label className="label"> 97 126 <span className="label-text font-bold mb-1 text-[14px]"> ··· 102 131 className="textarea max-w-full h-[250px] text-[14px] font-semibold" 103 132 aria-label="Textarea" 104 133 placeholder="File Content" 134 + {...register("content")} 105 135 ></textarea> 136 + {errors.content && ( 137 + <span className="text-error text-[12px] mt-1 block"> 138 + {errors.content.message} 139 + </span> 140 + )} 106 141 </div> 107 - </> 108 - </div> 109 - <div className="modal-footer"> 110 - <button 111 - className="btn btn-primary w-35 font-semibold" 112 - onClick={onAddFile} 113 - > 114 - Add File 115 - </button> 116 - </div> 142 + </div> 143 + <div className="modal-footer"> 144 + <button 145 + type="submit" 146 + className="btn btn-primary w-35 font-semibold" 147 + > 148 + Add File 149 + </button> 150 + </div> 151 + </form> 117 152 </div> 118 153 </div> 119 154 </div>
+48 -13
apps/web/src/components/contextmenu/AddSecretModal/AddSecretModal.tsx
··· 1 1 import { useEffect } from "react"; 2 2 import { createPortal } from "react-dom"; 3 + import { useForm } from "react-hook-form"; 4 + import { z } from "zod"; 5 + import { zodResolver } from "@hookform/resolvers/zod"; 3 6 import { useAddSecretMutation } from "../../../hooks/useSecret"; 4 7 8 + const schema = z.object({ 9 + name: z.string().min(1, "Name is required"), 10 + value: z.string().min(1, "Value is required"), 11 + }); 12 + 13 + type FormValues = z.infer<typeof schema>; 14 + 5 15 export type AddSecretModalProps = { 6 16 isOpen: boolean; 7 17 onClose: () => void; ··· 9 19 }; 10 20 11 21 function AddSecretModal({ isOpen, onClose, sandboxId }: AddSecretModalProps) { 12 - const { mutateAsync } = useAddSecretMutation(sandboxId, "", ""); 22 + const { mutateAsync } = useAddSecretMutation(); 23 + const { 24 + register, 25 + handleSubmit, 26 + reset, 27 + formState: { errors }, 28 + } = useForm<FormValues>({ 29 + resolver: zodResolver(schema), 30 + }); 31 + 13 32 useEffect(() => { 14 33 const handleEscapeKey = (event: KeyboardEvent) => { 15 34 if (event.key === "Escape" && isOpen) { ··· 39 58 onClose(); 40 59 }; 41 60 42 - const onAddSecret = (e: React.MouseEvent<HTMLButtonElement>) => { 43 - e.stopPropagation(); 61 + const onSubmit = async (data: FormValues) => { 62 + await mutateAsync({ 63 + sandboxId, 64 + name: data.name, 65 + value: data.value, 66 + }); 67 + reset(); 44 68 onClose(); 45 69 }; 46 70 ··· 73 97 <span className="icon-[tabler--x] size-4"></span> 74 98 </button> 75 99 </div> 76 - <div className="modal-body"> 77 - <> 100 + <form onSubmit={handleSubmit(onSubmit)}> 101 + <div className="modal-body"> 78 102 <div className="form-control w-full"> 79 103 <label className="label"> 80 104 <span className="label-text font-bold mb-1 text-[14px]"> ··· 87 111 placeholder="YOUR_SECRET_NAME" 88 112 className="grow" 89 113 autoComplete="off" 90 - name="search-filter" 91 114 data-1p-ignore 92 115 data-lpignore="true" 93 116 data-form-type="other" 117 + {...register("name")} 94 118 /> 95 119 </div> 120 + {errors.name && ( 121 + <span className="text-error text-[12px] mt-1"> 122 + {errors.name.message} 123 + </span> 124 + )} 96 125 <div className="mt-5"> 97 126 <label className="label"> 98 127 <span className="label-text font-bold mb-1 text-[14px]"> ··· 103 132 className="textarea max-w-full h-[250px] text-[14px] font-semibold" 104 133 aria-label="Textarea" 105 134 placeholder="Secret Value" 135 + {...register("value")} 106 136 ></textarea> 137 + {errors.value && ( 138 + <span className="text-error text-[12px] mt-1 block"> 139 + {errors.value.message} 140 + </span> 141 + )} 107 142 </div> 108 143 </div> 109 - </> 110 - </div> 111 - <div className="modal-footer"> 112 - <button className="btn btn-primary" onClick={onAddSecret}> 113 - Add Secret 114 - </button> 115 - </div> 144 + </div> 145 + <div className="modal-footer"> 146 + <button type="submit" className="btn btn-primary"> 147 + Add Secret 148 + </button> 149 + </div> 150 + </form> 116 151 </div> 117 152 </div> 118 153 </div>
+48 -17
apps/web/src/components/contextmenu/AddVolumeModal/AddVolumeModal.tsx
··· 1 1 import { useEffect } from "react"; 2 2 import { createPortal } from "react-dom"; 3 + import { useForm } from "react-hook-form"; 4 + import { z } from "zod"; 5 + import { zodResolver } from "@hookform/resolvers/zod"; 3 6 import { useAddVolumeMutation } from "../../../hooks/useVolume"; 4 7 8 + const schema = z.object({ 9 + name: z.string().min(1, "Name is required"), 10 + path: z.string().min(1, "Path is required"), 11 + }); 12 + 13 + type FormValues = z.infer<typeof schema>; 14 + 5 15 export type AddVolumeModalProps = { 6 16 isOpen: boolean; 7 17 onClose: () => void; ··· 9 19 }; 10 20 11 21 function AddVolumeModal({ isOpen, onClose, sandboxId }: AddVolumeModalProps) { 12 - const { mutateAsync } = useAddVolumeMutation(sandboxId, "", ""); 22 + const { mutateAsync } = useAddVolumeMutation(); 23 + const { 24 + register, 25 + handleSubmit, 26 + reset, 27 + formState: { errors }, 28 + } = useForm<FormValues>({ 29 + resolver: zodResolver(schema), 30 + }); 31 + 13 32 useEffect(() => { 14 33 const handleEscapeKey = (event: KeyboardEvent) => { 15 34 if (event.key === "Escape" && isOpen) { ··· 39 58 onClose(); 40 59 }; 41 60 42 - const onAddVolume = (e: React.MouseEvent<HTMLButtonElement>) => { 43 - e.stopPropagation(); 61 + const onSubmit = async (data: FormValues) => { 62 + await mutateAsync({ 63 + sandboxId, 64 + name: data.name, 65 + path: data.path, 66 + }); 67 + reset(); 44 68 onClose(); 45 69 }; 46 70 ··· 73 97 <span className="icon-[tabler--x] size-4"></span> 74 98 </button> 75 99 </div> 76 - <div className="modal-body " style={{ height: 300 }}> 77 - <> 100 + <form onSubmit={handleSubmit(onSubmit)}> 101 + <div className="modal-body" style={{ height: 300 }}> 78 102 <div className="form-control w-full"> 79 103 <label className="label"> 80 104 <span className="label-text font-bold mb-1 text-[14px]"> ··· 87 111 placeholder="Your Volume Name" 88 112 className="grow" 89 113 autoComplete="off" 90 - name="search-filter" 91 114 data-1p-ignore 92 115 data-lpignore="true" 93 116 data-form-type="other" 117 + {...register("name")} 94 118 /> 95 119 </div> 120 + {errors.name && ( 121 + <span className="text-error text-[12px] mt-1"> 122 + {errors.name.message} 123 + </span> 124 + )} 96 125 <div className="mt-5"> 97 126 <label className="label"> 98 127 <span className="label-text font-bold mb-1 text-[14px]"> ··· 105 134 placeholder="Mount Path, e.g /data" 106 135 className="grow" 107 136 autoComplete="off" 108 - name="search-filter" 109 137 data-1p-ignore 110 138 data-lpignore="true" 111 139 data-form-type="other" 140 + {...register("path")} 112 141 /> 113 142 </div> 143 + {errors.path && ( 144 + <span className="text-error text-[12px] mt-1 block"> 145 + {errors.path.message} 146 + </span> 147 + )} 114 148 </div> 115 149 </div> 116 - </> 117 - </div> 118 - <div className="modal-footer"> 119 - <button 120 - className="btn btn-primary font-semibold" 121 - onClick={onAddVolume} 122 - > 123 - Add Volume 124 - </button> 125 - </div> 150 + </div> 151 + <div className="modal-footer"> 152 + <button type="submit" className="btn btn-primary font-semibold"> 153 + Add Volume 154 + </button> 155 + </div> 156 + </form> 126 157 </div> 127 158 </div> 128 159 </div>
+12 -3
apps/web/src/components/contextmenu/DeleteSandboxModal/DeleteSandboxModal.tsx
··· 27 27 }; 28 28 }, [isOpen, onClose]); 29 29 30 + const onDeleteSandbox = async (e: React.MouseEvent<HTMLButtonElement>) => { 31 + await mutateAsync(sandboxId); 32 + e.stopPropagation(); 33 + onClose(); 34 + }; 35 + 30 36 const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => { 31 37 e.stopPropagation(); 32 38 if (e.target === e.currentTarget) { ··· 72 78 <span className="icon-[tabler--x] size-4"></span> 73 79 </button> 74 80 </div> 75 - <div className="modal-body p-0 pl-2 h-[200px]"> 76 - <></> 81 + <div className="modal-body p-0 pl-2 h-[100px]"> 82 + <p className="font-semibold text-center"> 83 + Are you sure you want to delete this sandbox? 84 + </p> 85 + <p className="text-center ">This action cannot be undone.</p> 77 86 </div> 78 87 <div className="modal-footer"> 79 88 <button 80 89 className="btn btn-error font-semibold" 81 - onClick={() => {}} 90 + onClick={onDeleteSandbox} 82 91 > 83 92 Delete Sandbox 84 93 </button>
+10 -6
apps/web/src/hooks/useFile.ts
··· 1 1 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 2 import { addFile, deleteFile, getFiles } from "../api/file"; 3 3 4 - export const useAddFileMutation = ( 5 - sandboxId: string, 6 - path: string, 7 - content: string, 8 - ) => { 4 + export const useAddFileMutation = () => { 9 5 const queryClient = useQueryClient(); 10 6 return useMutation({ 11 7 mutationKey: ["addFile"], 12 - mutationFn: async () => addFile(sandboxId, path, content), 8 + mutationFn: async ({ 9 + sandboxId, 10 + path, 11 + content, 12 + }: { 13 + sandboxId: string; 14 + path: string; 15 + content: string; 16 + }) => addFile(sandboxId, path, content), 13 17 onSuccess: () => { 14 18 queryClient.invalidateQueries({ queryKey: ["files"] }); 15 19 },
+10 -6
apps/web/src/hooks/useSecret.ts
··· 1 1 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 2 import { addSecret, deleteSecret, getSecrets } from "../api/secret"; 3 3 4 - export const useAddSecretMutation = ( 5 - sandboxId: string, 6 - name: string, 7 - value: string, 8 - ) => { 4 + export const useAddSecretMutation = () => { 9 5 const queryClient = useQueryClient(); 10 6 return useMutation({ 11 7 mutationKey: ["addSecret"], 12 - mutationFn: async () => addSecret(sandboxId, name, value), 8 + mutationFn: async ({ 9 + sandboxId, 10 + name, 11 + value, 12 + }: { 13 + sandboxId: string; 14 + name: string; 15 + value: string; 16 + }) => addSecret(sandboxId, name, value), 13 17 onSuccess: () => { 14 18 queryClient.invalidateQueries({ queryKey: ["secrets"] }); 15 19 },
+10 -6
apps/web/src/hooks/useVariable.ts
··· 1 1 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 2 import { addVariable, deleteVariable, getVariables } from "../api/variable"; 3 3 4 - export const useAddVariableMutation = ( 5 - sandboxId: string, 6 - name: string, 7 - value: string, 8 - ) => { 4 + export const useAddVariableMutation = () => { 9 5 const queryClient = useQueryClient(); 10 6 return useMutation({ 11 7 mutationKey: ["addVariable"], 12 - mutationFn: async () => addVariable(sandboxId, name, value), 8 + mutationFn: async ({ 9 + sandboxId, 10 + name, 11 + value, 12 + }: { 13 + sandboxId: string; 14 + name: string; 15 + value: string; 16 + }) => addVariable(sandboxId, name, value), 13 17 onSuccess: () => { 14 18 queryClient.invalidateQueries({ queryKey: ["variables"] }); 15 19 },
+10 -6
apps/web/src/hooks/useVolume.ts
··· 1 1 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 2 2 import { addVolume, deleteVolume, getVolumes } from "../api/volume"; 3 3 4 - export const useAddVolumeMutation = ( 5 - sandboxId: string, 6 - name: string, 7 - path: string, 8 - ) => { 4 + export const useAddVolumeMutation = () => { 9 5 const queryClient = useQueryClient(); 10 6 return useMutation({ 11 - mutationFn: async () => addVolume(sandboxId, name, path), 7 + mutationFn: async ({ 8 + sandboxId, 9 + name, 10 + path, 11 + }: { 12 + sandboxId: string; 13 + name: string; 14 + path: string; 15 + }) => addVolume(sandboxId, name, path), 12 16 onSuccess: () => { 13 17 queryClient.invalidateQueries({ queryKey: ["volumes"] }); 14 18 },