Merge pull request #3174 from Infisical/feat/addFolderDescription

Add descriptions to secret folders
This commit is contained in:
carlosmonastyrski
2025-03-04 14:56:43 -03:00
committed by GitHub
14 changed files with 168 additions and 43 deletions
@@ -0,0 +1,23 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
const hasProjectDescription = await knex.schema.hasColumn(TableName.SecretFolder, "description");
if (!hasProjectDescription) {
await knex.schema.alterTable(TableName.SecretFolder, (t) => {
t.string("description");
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasProjectDescription = await knex.schema.hasColumn(TableName.SecretFolder, "description");
if (hasProjectDescription) {
await knex.schema.alterTable(TableName.SecretFolder, (t) => {
t.dropColumn("description");
});
}
}
+2 -1
View File
@@ -15,7 +15,8 @@ export const SecretFoldersSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
envId: z.string().uuid(), envId: z.string().uuid(),
parentId: z.string().uuid().nullable().optional(), parentId: z.string().uuid().nullable().optional(),
isReserved: z.boolean().default(false).nullable().optional() isReserved: z.boolean().default(false).nullable().optional(),
description: z.string().nullable().optional()
}); });
export type TSecretFolders = z.infer<typeof SecretFoldersSchema>; export type TSecretFolders = z.infer<typeof SecretFoldersSchema>;
@@ -1142,6 +1142,7 @@ interface CreateFolderEvent {
folderId: string; folderId: string;
folderName: string; folderName: string;
folderPath: string; folderPath: string;
description?: string;
}; };
} }
+4 -2
View File
@@ -638,7 +638,8 @@ export const FOLDERS = {
environment: "The slug of the environment to create the folder in.", environment: "The slug of the environment to create the folder in.",
name: "The name of the folder to create.", name: "The name of the folder to create.",
path: "The path of the folder to create.", path: "The path of the folder to create.",
directory: "The directory of the folder to create. (Deprecated in favor of path)" directory: "The directory of the folder to create. (Deprecated in favor of path)",
description: "An optional description label for the folder."
}, },
UPDATE: { UPDATE: {
folderId: "The ID of the folder to update.", folderId: "The ID of the folder to update.",
@@ -647,7 +648,8 @@ export const FOLDERS = {
path: "The path of the folder to update.", path: "The path of the folder to update.",
directory: "The new directory of the folder to update. (Deprecated in favor of path)", directory: "The new directory of the folder to update. (Deprecated in favor of path)",
projectSlug: "The slug of the project where the folder is located.", projectSlug: "The slug of the project where the folder is located.",
workspaceId: "The ID of the project where the folder is located." workspaceId: "The ID of the project where the folder is located.",
description: "An optional description label for the folder."
}, },
DELETE: { DELETE: {
folderIdOrName: "The ID or name of the folder to delete.", folderIdOrName: "The ID or name of the folder to delete.",
@@ -47,7 +47,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.default("/") .default("/")
.transform(prefixWithSlash) .transform(prefixWithSlash)
.transform(removeTrailingSlash) .transform(removeTrailingSlash)
.describe(FOLDERS.CREATE.directory) .describe(FOLDERS.CREATE.directory),
description: z.string().optional().nullable().describe(FOLDERS.CREATE.description)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -65,7 +66,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
...req.body, ...req.body,
projectId: req.body.workspaceId, projectId: req.body.workspaceId,
path path,
description: req.body.description
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
@@ -76,7 +78,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
environment: req.body.environment, environment: req.body.environment,
folderId: folder.id, folderId: folder.id,
folderName: folder.name, folderName: folder.name,
folderPath: path folderPath: path,
...(req.body.description ? { description: req.body.description } : {})
} }
} }
}); });
@@ -125,7 +128,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.default("/") .default("/")
.transform(prefixWithSlash) .transform(prefixWithSlash)
.transform(removeTrailingSlash) .transform(removeTrailingSlash)
.describe(FOLDERS.UPDATE.directory) .describe(FOLDERS.UPDATE.directory),
description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -196,7 +200,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.default("/") .default("/")
.transform(prefixWithSlash) .transform(prefixWithSlash)
.transform(removeTrailingSlash) .transform(removeTrailingSlash)
.describe(FOLDERS.UPDATE.path) .describe(FOLDERS.UPDATE.path),
description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description)
}) })
.array() .array()
.min(1) .min(1)
@@ -50,7 +50,8 @@ export const secretFolderServiceFactory = ({
actorOrgId, actorOrgId,
name, name,
environment, environment,
path: secretPath path: secretPath,
description
}: TCreateFolderDTO) => { }: TCreateFolderDTO) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
@@ -121,7 +122,10 @@ export const secretFolderServiceFactory = ({
} }
} }
const doc = await folderDAL.create({ name, envId: env.id, version: 1, parentId: parentFolderId }, tx); const doc = await folderDAL.create(
{ name, envId: env.id, version: 1, parentId: parentFolderId, description },
tx
);
await folderVersionDAL.create( await folderVersionDAL.create(
{ {
name: doc.name, name: doc.name,
@@ -170,7 +174,7 @@ export const secretFolderServiceFactory = ({
const result = await folderDAL.transaction(async (tx) => const result = await folderDAL.transaction(async (tx) =>
Promise.all( Promise.all(
folders.map(async (newFolder) => { folders.map(async (newFolder) => {
const { environment, path: secretPath, id, name } = newFolder; const { environment, path: secretPath, id, name, description } = newFolder;
const parentFolder = await folderDAL.findBySecretPath(project.id, environment, secretPath); const parentFolder = await folderDAL.findBySecretPath(project.id, environment, secretPath);
if (!parentFolder) { if (!parentFolder) {
@@ -217,7 +221,7 @@ export const secretFolderServiceFactory = ({
const [doc] = await folderDAL.update( const [doc] = await folderDAL.update(
{ envId: env.id, id: folder.id, parentId: parentFolder.id }, { envId: env.id, id: folder.id, parentId: parentFolder.id },
{ name }, { name, description },
tx tx
); );
await folderVersionDAL.create( await folderVersionDAL.create(
@@ -259,7 +263,8 @@ export const secretFolderServiceFactory = ({
name, name,
environment, environment,
path: secretPath, path: secretPath,
id id,
description
}: TUpdateFolderDTO) => { }: TUpdateFolderDTO) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
@@ -312,7 +317,7 @@ export const secretFolderServiceFactory = ({
const newFolder = await folderDAL.transaction(async (tx) => { const newFolder = await folderDAL.transaction(async (tx) => {
const [doc] = await folderDAL.update( const [doc] = await folderDAL.update(
{ envId: env.id, id: folder.id, parentId: parentFolder.id, isReserved: false }, { envId: env.id, id: folder.id, parentId: parentFolder.id, isReserved: false },
{ name }, { name, description },
tx tx
); );
await folderVersionDAL.create( await folderVersionDAL.create(
@@ -9,6 +9,7 @@ export type TCreateFolderDTO = {
environment: string; environment: string;
path: string; path: string;
name: string; name: string;
description?: string | null;
} & TProjectPermission; } & TProjectPermission;
export type TUpdateFolderDTO = { export type TUpdateFolderDTO = {
@@ -16,6 +17,7 @@ export type TUpdateFolderDTO = {
path: string; path: string;
id: string; id: string;
name: string; name: string;
description?: string | null;
} & TProjectPermission; } & TProjectPermission;
export type TUpdateManyFoldersDTO = { export type TUpdateManyFoldersDTO = {
@@ -25,6 +27,7 @@ export type TUpdateManyFoldersDTO = {
path: string; path: string;
id: string; id: string;
name: string; name: string;
description?: string | null;
}[]; }[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
@@ -148,12 +148,13 @@ export const useUpdateFolder = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<object, object, TUpdateFolderDTO>({ return useMutation<object, object, TUpdateFolderDTO>({
mutationFn: async ({ path = "/", folderId, name, environment, projectId }) => { mutationFn: async ({ path = "/", folderId, name, environment, projectId, description }) => {
const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, { const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, {
name, name,
environment, environment,
workspaceId: projectId, workspaceId: projectId,
path path,
description
}); });
return data; return data;
}, },
@@ -5,6 +5,7 @@ export enum ReservedFolders {
export type TSecretFolder = { export type TSecretFolder = {
id: string; id: string;
name: string; name: string;
description?: string;
}; };
export type TGetProjectFoldersDTO = { export type TGetProjectFoldersDTO = {
@@ -24,6 +25,7 @@ export type TCreateFolderDTO = {
environment: string; environment: string;
name: string; name: string;
path?: string; path?: string;
description?: string | null;
}; };
export type TUpdateFolderDTO = { export type TUpdateFolderDTO = {
@@ -32,6 +34,7 @@ export type TUpdateFolderDTO = {
name: string; name: string;
folderId: string; folderId: string;
path?: string; path?: string;
description?: string | null;
}; };
export type TDeleteFolderDTO = { export type TDeleteFolderDTO = {
@@ -49,5 +52,6 @@ export type TUpdateFolderBatchDTO = {
environment: string; environment: string;
id: string; id: string;
path?: string; path?: string;
description?: string | null;
}[]; }[];
}; };
+14 -5
View File
@@ -2,13 +2,22 @@ import { useCallback, useMemo } from "react";
import { DashboardProjectSecretsOverview } from "@app/hooks/api/dashboard/types"; import { DashboardProjectSecretsOverview } from "@app/hooks/api/dashboard/types";
type FolderNameAndDescription = {
name: string;
description?: string;
};
export const useFolderOverview = (folders: DashboardProjectSecretsOverview["folders"]) => { export const useFolderOverview = (folders: DashboardProjectSecretsOverview["folders"]) => {
const folderNames = useMemo(() => { const folderNamesAndDescriptions = useMemo(() => {
const names = new Set<string>(); const namesAndDescriptions = new Map<string, FolderNameAndDescription>();
folders?.forEach((folder) => { folders?.forEach((folder) => {
names.add(folder.name); if (!namesAndDescriptions.has(folder.name)) {
namesAndDescriptions.set(folder.name, { name: folder.name, description: folder.description });
}
}); });
return [...names];
return Array.from(namesAndDescriptions.values());
}, [folders]); }, [folders]);
const isFolderPresentInEnv = useCallback( const isFolderPresentInEnv = useCallback(
@@ -31,7 +40,7 @@ export const useFolderOverview = (folders: DashboardProjectSecretsOverview["fold
[folders] [folders]
); );
return { folderNames, isFolderPresentInEnv, getFolderByNameAndEnv }; return { folderNamesAndDescriptions, isFolderPresentInEnv, getFolderByNameAndEnv };
}; };
export const useDynamicSecretOverview = ( export const useDynamicSecretOverview = (
@@ -228,7 +228,7 @@ export const OverviewPage = () => {
setPage setPage
}); });
const { folderNames, getFolderByNameAndEnv, isFolderPresentInEnv } = useFolderOverview(folders); const { folderNamesAndDescriptions, getFolderByNameAndEnv, isFolderPresentInEnv } = useFolderOverview(folders);
const { dynamicSecretNames, isDynamicSecretPresentInEnv } = const { dynamicSecretNames, isDynamicSecretPresentInEnv } =
useDynamicSecretOverview(dynamicSecrets); useDynamicSecretOverview(dynamicSecrets);
@@ -251,14 +251,15 @@ export const OverviewPage = () => {
"updateFolder" "updateFolder"
] as const); ] as const);
const handleFolderCreate = async (folderName: string) => { const handleFolderCreate = async (folderName: string, description: string | null) => {
const promises = userAvailableEnvs.map((env) => { const promises = userAvailableEnvs.map((env) => {
const environment = env.slug; const environment = env.slug;
return createFolder({ return createFolder({
name: folderName, name: folderName,
path: secretPath, path: secretPath,
environment, environment,
projectId: workspaceId projectId: workspaceId,
description
}); });
}); });
@@ -279,7 +280,7 @@ export const OverviewPage = () => {
} }
}; };
const handleFolderUpdate = async (newFolderName: string) => { const handleFolderUpdate = async (newFolderName: string, description: string | null) => {
const { name: oldFolderName } = popUp.updateFolder.data as TSecretFolder; const { name: oldFolderName } = popUp.updateFolder.data as TSecretFolder;
const updatedFolders: TUpdateFolderBatchDTO["folders"] = []; const updatedFolders: TUpdateFolderBatchDTO["folders"] = [];
@@ -296,7 +297,8 @@ export const OverviewPage = () => {
environment: env.slug, environment: env.slug,
name: newFolderName, name: newFolderName,
id: folder.id, id: folder.id,
path: secretPath path: secretPath,
description
}); });
} }
} }
@@ -1027,7 +1029,7 @@ export const OverviewPage = () => {
)} )}
{!isOverviewLoading && visibleEnvs.length > 0 && ( {!isOverviewLoading && visibleEnvs.length > 0 && (
<> <>
{folderNames.map((folderName, index) => ( {folderNamesAndDescriptions.map(({name: folderName, description}, index) => (
<SecretOverviewFolderRow <SecretOverviewFolderRow
folderName={folderName} folderName={folderName}
isFolderPresentInEnv={isFolderPresentInEnv} isFolderPresentInEnv={isFolderPresentInEnv}
@@ -1039,7 +1041,7 @@ export const OverviewPage = () => {
key={`overview-${folderName}-${index + 1}`} key={`overview-${folderName}-${index + 1}`}
onClick={handleFolderClick} onClick={handleFolderClick}
onToggleFolderEdit={(name: string) => onToggleFolderEdit={(name: string) =>
handlePopUpOpen("updateFolder", { name }) handlePopUpOpen("updateFolder", { name, description })
} }
/> />
))} ))}
@@ -1159,7 +1161,9 @@ export const OverviewPage = () => {
<FolderForm <FolderForm
isEdit isEdit
defaultFolderName={(popUp.updateFolder?.data as Pick<TSecretFolder, "name">)?.name} defaultFolderName={(popUp.updateFolder?.data as Pick<TSecretFolder, "name">)?.name}
defaultDescription={(popUp.updateFolder?.data as Pick<TSecretFolder, "description">)?.description}
onUpdateFolder={handleFolderUpdate} onUpdateFolder={handleFolderUpdate}
showDescriptionOverwriteWarning
/> />
</ModalContent> </ModalContent>
</Modal> </Modal>
@@ -128,13 +128,14 @@ export const ActionBar = ({
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const handleFolderCreate = async (folderName: string) => { const handleFolderCreate = async (folderName: string, description: string | null) => {
try { try {
await createFolder({ await createFolder({
name: folderName, name: folderName,
path: secretPath, path: secretPath,
environment, environment,
projectId: workspaceId projectId: workspaceId,
description
}); });
handlePopUpClose("addFolder"); handlePopUpClose("addFolder");
createNotification({ createNotification({
@@ -1,16 +1,22 @@
import { useRef } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { Button, FormControl, Input, ModalClose } from "@app/components/v2"; import { Button, FormControl, Input, ModalClose } from "@app/components/v2";
import { TextArea } from "@app/components/v2/TextArea/TextArea";
type Props = { type Props = {
onCreateFolder?: (folderName: string) => Promise<void>; onCreateFolder?: (folderName: string, description: string | null) => Promise<void>;
onUpdateFolder?: (folderName: string) => Promise<void>; onUpdateFolder?: (folderName: string, description: string | null) => Promise<void>;
isEdit?: boolean; isEdit?: boolean;
defaultFolderName?: string; defaultFolderName?: string;
defaultDescription?: string;
showDescriptionOverwriteWarning?: boolean;
}; };
const descriptionOverwriteWarningMessage = "Warning: Any changes made here will overwrite any custom edits in individual environment folders."
const formSchema = z.object({ const formSchema = z.object({
name: z name: z
.string() .string()
@@ -18,15 +24,20 @@ const formSchema = z.object({
.regex( .regex(
/^[a-zA-Z0-9-_]+$/, /^[a-zA-Z0-9-_]+$/,
"Folder name can only contain letters, numbers, dashes, and underscores" "Folder name can only contain letters, numbers, dashes, and underscores"
) ),
description: z
.string()
.optional()
}); });
type TFormData = z.infer<typeof formSchema>; type TFormData = z.infer<typeof formSchema>;
export const FolderForm = ({ export const FolderForm = ({
isEdit, isEdit,
defaultFolderName, defaultFolderName,
defaultDescription,
onCreateFolder, onCreateFolder,
onUpdateFolder onUpdateFolder,
showDescriptionOverwriteWarning = false
}: Props): JSX.Element => { }: Props): JSX.Element => {
const { const {
control, control,
@@ -36,15 +47,32 @@ export const FolderForm = ({
} = useForm<TFormData>({ } = useForm<TFormData>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
name: defaultFolderName name: defaultFolderName,
description: defaultDescription || ""
} }
}); });
const onSubmit = async ({ name }: TFormData) => { const descriptionRef = useRef<HTMLTextAreaElement>(null);
const handleInput = () => {
const textarea = descriptionRef.current;
if (textarea) {
const lines = textarea.value.split("\n");
const maxDescriptionLines = 10;
if (lines.length > maxDescriptionLines) {
textarea.value = lines.slice(0, maxDescriptionLines).join("\n");
}
}
};
const onSubmit = async ({ name, description }: TFormData) => {
const descriptionShaped = description && description.trim() !== "" ? description : null;
if (isEdit) { if (isEdit) {
await onUpdateFolder?.(name); await onUpdateFolder?.(name, descriptionShaped);
} else { } else {
await onCreateFolder?.(name); await onCreateFolder?.(name, descriptionShaped);
} }
reset(); reset();
}; };
@@ -61,6 +89,31 @@ export const FolderForm = ({
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="description"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Folder Description"
isError={Boolean(error)}
tooltipText={showDescriptionOverwriteWarning ? descriptionOverwriteWarningMessage : undefined}
isOptional
errorText={error?.message}
className="flex-1"
>
<TextArea
placeholder="Folder description"
{...field}
rows={3}
ref={descriptionRef}
onInput={handleInput}
className="thin-scrollbar w-full !resize-none bg-mineshaft-900"
maxLength={255}
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center"> <div className="mt-8 flex items-center">
<Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}> <Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
{isEdit ? "Save" : "Create"} {isEdit ? "Save" : "Create"}
@@ -1,5 +1,5 @@
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { faClose, faFolder, faPencilSquare } from "@fortawesome/free-solid-svg-icons"; import { faClose, faFolder, faPencilSquare, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router"; import { useNavigate, useSearch } from "@tanstack/react-router";
@@ -11,6 +11,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteFolder, useUpdateFolder } from "@app/hooks/api"; import { useDeleteFolder, useUpdateFolder } from "@app/hooks/api";
import { TSecretFolder } from "@app/hooks/api/secretFolders/types"; import { TSecretFolder } from "@app/hooks/api/secretFolders/types";
import { Tooltip } from "@app/components/v2/Tooltip/Tooltip";
import { FolderForm } from "../ActionBar/FolderForm"; import { FolderForm } from "../ActionBar/FolderForm";
@@ -42,7 +43,7 @@ export const FolderListView = ({
const { mutateAsync: updateFolder } = useUpdateFolder(); const { mutateAsync: updateFolder } = useUpdateFolder();
const { mutateAsync: deleteFolder } = useDeleteFolder(); const { mutateAsync: deleteFolder } = useDeleteFolder();
const handleFolderUpdate = async (newFolderName: string) => { const handleFolderUpdate = async (newFolderName: string, newFolderDescription: string | null) => {
try { try {
const { id: folderId } = popUp.updateFolder.data as TSecretFolder; const { id: folderId } = popUp.updateFolder.data as TSecretFolder;
await updateFolder({ await updateFolder({
@@ -50,7 +51,8 @@ export const FolderListView = ({
name: newFolderName, name: newFolderName,
path: secretPath, path: secretPath,
environment, environment,
projectId: workspaceId projectId: workspaceId,
description: newFolderDescription
}); });
handlePopUpClose("updateFolder"); handlePopUpClose("updateFolder");
createNotification({ createNotification({
@@ -98,7 +100,7 @@ export const FolderListView = ({
return ( return (
<> <>
{folders.map(({ name, id }) => ( {folders.map(({ name, id, description }) => (
<div <div
key={id} key={id}
className="group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700" className="group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700"
@@ -116,6 +118,16 @@ export const FolderListView = ({
onClick={() => handleFolderClick(name)} onClick={() => handleFolderClick(name)}
> >
{name} {name}
{
description &&
<Tooltip
position="right"
className="flex items-center space-x-4 max-w-lg py-4 whitespace-pre-wrap"
content={description}
>
<FontAwesomeIcon icon={faInfoCircle} className="text-mineshaft-400 ml-1" />
</Tooltip>
}
</div> </div>
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3"> <div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan <ProjectPermissionCan
@@ -130,7 +142,7 @@ export const FolderListView = ({
variant="plain" variant="plain"
size="sm" size="sm"
className="p-0 opacity-0 group-hover:opacity-100" className="p-0 opacity-0 group-hover:opacity-100"
onClick={() => handlePopUpOpen("updateFolder", { id, name })} onClick={() => handlePopUpOpen("updateFolder", { id, name, description })}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faPencilSquare} size="lg" /> <FontAwesomeIcon icon={faPencilSquare} size="lg" />
@@ -167,6 +179,7 @@ export const FolderListView = ({
<FolderForm <FolderForm
isEdit isEdit
defaultFolderName={(popUp.updateFolder?.data as TSecretFolder)?.name} defaultFolderName={(popUp.updateFolder?.data as TSecretFolder)?.name}
defaultDescription={(popUp.updateFolder?.data as TSecretFolder)?.description}
onUpdateFolder={handleFolderUpdate} onUpdateFolder={handleFolderUpdate}
/> />
</ModalContent> </ModalContent>