From 820b7e655a670cf8b8e3f7ad8bb1eb487ee20405 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 14 Sep 2025 10:37:36 +0100 Subject: feat: Add Create Tag button to tags page (#1942) * feat: add Create Tag button to tags page - Added useCreateTag hook to shared-react/hooks/tags.ts - Created CreateTagModal component for tag creation without bookmark attachment - Added Create Tag button to AllTagsView component - Added necessary translation keys for the new feature Fixes #1937 Co-authored-by: Mohamed Bassem * format * localize toasts --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Mohamed Bassem --- apps/web/components/dashboard/tags/AllTagsView.tsx | 2 + .../components/dashboard/tags/CreateTagModal.tsx | 145 +++++++++++++++++++++ apps/web/lib/i18n/locales/en/translation.json | 10 +- 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 apps/web/components/dashboard/tags/CreateTagModal.tsx (limited to 'apps') diff --git a/apps/web/components/dashboard/tags/AllTagsView.tsx b/apps/web/components/dashboard/tags/AllTagsView.tsx index 965afa73..73d9a595 100644 --- a/apps/web/components/dashboard/tags/AllTagsView.tsx +++ b/apps/web/components/dashboard/tags/AllTagsView.tsx @@ -29,6 +29,7 @@ import type { ZGetTagResponse, ZTagBasic } from "@karakeep/shared/types/tags"; import { useDeleteUnusedTags } from "@karakeep/shared-react/hooks/tags"; import BulkTagAction from "./BulkTagAction"; +import { CreateTagModal } from "./CreateTagModal"; import DeleteTagConfirmationDialog from "./DeleteTagConfirmationDialog"; import { MultiTagSelector } from "./MultiTagSelector"; import { TagPill } from "./TagPill"; @@ -184,6 +185,7 @@ export default function AllTagsView({
{t("tags.all_tags")}
+ >({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + }, + }); + + const { mutate: createTag, isPending } = useCreateTag({ + onSuccess: () => { + toast({ + description: t("toasts.tags.created"), + }); + setOpen(false); + form.reset(); + }, + onError: (e) => { + if (e.data?.code === "BAD_REQUEST") { + if (e.data.zodError) { + toast({ + variant: "destructive", + description: Object.values(e.data.zodError.fieldErrors) + .flat() + .join("\n"), + }); + } else { + toast({ + variant: "destructive", + description: e.message, + }); + } + } else { + toast({ + variant: "destructive", + title: t("common.something_went_wrong"), + description: t("toasts.tags.failed_to_create"), + }); + } + }, + }); + + return ( + { + setOpen(isOpen); + if (!isOpen) { + form.reset(); + } + }} + > + + + + +
+ { + createTag(values); + })} + > + + {t("tags.create_tag")} + + {t("tags.create_tag_description")} + + +
+ ( + + {t("tags.tag_name")} + + + + + + )} + /> +
+ + + + + + {t("actions.create")} + + +
+ +
+
+ ); +} diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index a5dddd56..065b5ed6 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -443,7 +443,11 @@ "delete_all_unused_tags": "Delete All Unused Tags", "drag_and_drop_merging": "Drag & Drop Merging", "drag_and_drop_merging_info": "Drag and drop tags on each other to merge them", - "sort_by_name": "Sort by Name" + "sort_by_name": "Sort by Name", + "create_tag": "Create Tag", + "create_tag_description": "Create a new tag without attaching it to any bookmark", + "tag_name": "Tag Name", + "enter_tag_name": "Enter tag name" }, "search": { "is_favorited": "Is Favorited", @@ -573,6 +577,10 @@ "updated": "List has been updated!", "merged": "List has been merged!", "deleted": "List has been deleted!" + }, + "tags": { + "created": "Tag has been created!", + "failed_to_create": "Failed to create tag" } }, "banners": { -- cgit v1.2.3-70-g09d2