feat(library): enhance folder management with validation and UI improvements

- Integrated folder existence validation in both `LibraryFolderCreationDialog` and `LibraryFolderEditDialog` to prevent duplicate folder names.
- Updated folder name handling to trim whitespace before submission.
- Improved UI spacing in folder dialogs for better user experience.
- Added API integration for fetching existing folders to support validation.

These changes enhance the usability and reliability of folder management in the library, ensuring users can create and edit folders without naming conflicts.
This commit is contained in:
abhi1992002
2026-02-13 14:21:50 +05:30
parent dce3d26d0a
commit a6c2f645f1
2 changed files with 56 additions and 5 deletions

View File

@@ -19,8 +19,10 @@ import { z } from "zod";
import { EmojiPicker } from "@ferrucc-io/emoji-picker";
import {
usePostV2CreateFolder,
useGetV2ListLibraryFolders,
getGetV2ListLibraryFoldersQueryKey,
} from "@/app/api/__generated__/endpoints/folders/folders";
import { okData } from "@/app/api/helpers";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useQueryClient } from "@tanstack/react-query";
@@ -43,6 +45,10 @@ export default function LibraryFolderCreationDialog() {
const queryClient = useQueryClient();
const { toast } = useToast();
const { data: foldersData } = useGetV2ListLibraryFolders(undefined, {
query: { select: okData },
});
const { mutate: createFolder, isPending } = usePostV2CreateFolder({
mutation: {
onSuccess: () => {
@@ -74,9 +80,19 @@ export default function LibraryFolderCreationDialog() {
});
function onSubmit(values: z.infer<typeof libraryFolderCreationFormSchema>) {
const existingNames = (foldersData?.folders ?? []).map((f) =>
f.name.toLowerCase(),
);
if (existingNames.includes(values.folderName.trim().toLowerCase())) {
form.setError("folderName", {
message: "A folder with this name already exists",
});
return;
}
createFolder({
data: {
name: values.folderName,
name: values.folderName.trim(),
color: values.folderColor,
icon: values.folderIcon,
},
@@ -110,7 +126,7 @@ export default function LibraryFolderCreationDialog() {
<Form
form={form}
onSubmit={(values) => onSubmit(values)}
className="flex flex-col justify-center gap-4 px-1"
className="flex flex-col justify-center px-1 gap-2"
>
<FormField
control={form.control}
@@ -123,7 +139,8 @@ export default function LibraryFolderCreationDialog() {
id={field.name}
label="Folder name"
placeholder="Enter folder name"
className="w-full"
className="w-full !mb-0"
wrapperClassName="!mb-0"
/>
</FormControl>
<FormMessage />
@@ -153,6 +170,7 @@ export default function LibraryFolderCreationDialog() {
/>
),
}))}
wrapperClassName="!mb-0"
renderItem={(option) => (
<div className="flex items-center gap-2">
{option.icon}

View File

@@ -20,8 +20,10 @@ import { z } from "zod";
import { EmojiPicker } from "@ferrucc-io/emoji-picker";
import {
usePatchV2UpdateFolder,
useGetV2ListLibraryFolders,
getGetV2ListLibraryFoldersQueryKey,
} from "@/app/api/__generated__/endpoints/folders/folders";
import { okData } from "@/app/api/helpers";
import { useQueryClient } from "@tanstack/react-query";
import type { LibraryFolder } from "@/app/api/__generated__/models/libraryFolder";
import type { getV2ListLibraryFoldersResponseSuccess } from "@/app/api/__generated__/endpoints/folders/folders";
@@ -50,6 +52,10 @@ export function LibraryFolderEditDialog({ folder, isOpen, setIsOpen }: Props) {
const queryClient = useQueryClient();
const { toast } = useToast();
const { data: foldersData } = useGetV2ListLibraryFolders(undefined, {
query: { select: okData },
});
const form = useForm<z.infer<typeof editFolderSchema>>({
resolver: zodResolver(editFolderSchema),
defaultValues: {
@@ -107,12 +113,27 @@ export function LibraryFolderEditDialog({ folder, isOpen, setIsOpen }: Props) {
return { previousData };
},
onError: (_error, _variables, context) => {
onError: (
error: { detail?: string; response?: { detail?: string } },
_variables,
context,
) => {
if (context?.previousData) {
for (const [queryKey, data] of context.previousData) {
queryClient.setQueryData(queryKey, data);
}
}
const detail =
error.detail ?? error.response?.detail ?? "";
if (
typeof detail === "string" &&
detail.toLowerCase().includes("already exists")
) {
form.setError("folderName", {
message: "A folder with this name already exists",
});
return;
}
toast({
title: "Error",
description: "Failed to update folder. Please try again.",
@@ -135,10 +156,22 @@ export function LibraryFolderEditDialog({ folder, isOpen, setIsOpen }: Props) {
});
function onSubmit(values: z.infer<typeof editFolderSchema>) {
const trimmedName = values.folderName.trim();
const existingNames = (foldersData?.folders ?? [])
.filter((f) => f.id !== folder.id)
.map((f) => f.name.toLowerCase());
if (existingNames.includes(trimmedName.toLowerCase())) {
form.setError("folderName", {
message: "A folder with this name already exists",
});
return;
}
updateFolder({
folderId: folder.id,
data: {
name: values.folderName,
name: trimmedName,
color: values.folderColor,
icon: values.folderIcon,
},