diff --git a/components/InputSelect/TagSelection.tsx b/components/InputSelect/TagSelection.tsx index d901b40..efd246b 100644 --- a/components/InputSelect/TagSelection.tsx +++ b/components/InputSelect/TagSelection.tsx @@ -1,8 +1,8 @@ -import useTagStore from "@/store/tags"; import { useEffect, useState } from "react"; import CreatableSelect from "react-select/creatable"; import { styles } from "./styles"; import { Options } from "./types"; +import { useTags } from "@/hooks/store/tags"; type Props = { onChange: any; @@ -13,12 +13,12 @@ type Props = { }; export default function TagSelection({ onChange, defaultValue }: Props) { - const { tags } = useTagStore(); + const { data: tags = [] } = useTags(); const [options, setOptions] = useState([]); useEffect(() => { - const formatedCollections = tags.map((e) => { + const formatedCollections = tags.map((e: any) => { return { value: e.id, label: e.name }; }); diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index d3cde02..4348571 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -1,4 +1,3 @@ -import useTagStore from "@/store/tags"; import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; @@ -7,6 +6,7 @@ import SidebarHighlightLink from "@/components/SidebarHighlightLink"; import CollectionListing from "@/components/CollectionListing"; import { useTranslation } from "next-i18next"; import { useCollections } from "@/hooks/store/collections"; +import { useTags } from "@/hooks/store/tags"; export default function Sidebar({ className }: { className?: string }) { const { t } = useTranslation(); @@ -24,7 +24,7 @@ export default function Sidebar({ className }: { className?: string }) { const { data: collections = [] } = useCollections(); - const { tags } = useTagStore(); + const { data: tags = [] } = useTags(); const [active, setActive] = useState(""); const router = useRouter(); @@ -130,8 +130,8 @@ export default function Sidebar({ className }: { className?: string }) { {tags[0] ? ( tags - .sort((a, b) => a.name.localeCompare(b.name)) - .map((e, i) => { + .sort((a: any, b: any) => a.name.localeCompare(b.name)) + .map((e: any, i: any) => { return (
{ + return useQuery({ + queryKey: ["tags"], + queryFn: async () => { + const response = await fetch("/api/v1/tags"); + if (!response.ok) throw new Error("Failed to fetch tags."); + + const data = await response.json(); + return data.response; + }, + }); +}; + +const useUpdateTag = () => { + const queryClient = useQueryClient(); + const { t } = useTranslation(); + + return useMutation({ + mutationFn: async (tag: TagIncludingLinkCount) => { + const load = toast.loading(t("applying_changes")); + + const response = await fetch(`/api/v1/tags/${tag.id}`, { + body: JSON.stringify(tag), + headers: { + "Content-Type": "application/json", + }, + method: "PUT", + }); + + const data = await response.json(); + if (!response.ok) throw new Error(data.response); + + toast.dismiss(load); + + return data.response; + }, + onSuccess: (data) => { + queryClient.setQueryData(["tags"], (oldData: any) => + oldData.map((tag: TagIncludingLinkCount) => + tag.id === data.id ? data : tag + ) + ); + toast.success(t("tag_renamed")); + }, + onError: (error) => { + toast.error(error.message); + }, + }); +}; + +const useRemoveTag = () => { + const queryClient = useQueryClient(); + const { t } = useTranslation(); + + return useMutation({ + mutationFn: async (tagId: number) => { + const load = toast.loading(t("applying_changes")); + + const response = await fetch(`/api/v1/tags/${tagId}`, { + method: "DELETE", + }); + + const data = await response.json(); + if (!response.ok) throw new Error(data.response); + + toast.dismiss(load); + + return data.response; + }, + onSuccess: (data, variables) => { + queryClient.setQueryData(["tags"], (oldData: any) => + oldData.filter((tag: TagIncludingLinkCount) => tag.id !== variables) + ); + toast.success(t("tag_deleted")); + }, + onError: (error) => { + toast.error(error.message); + }, + }); +}; + +export { useTags, useUpdateTag, useRemoveTag }; diff --git a/hooks/useInitialData.tsx b/hooks/useInitialData.tsx index cf91623..fab5241 100644 --- a/hooks/useInitialData.tsx +++ b/hooks/useInitialData.tsx @@ -1,13 +1,10 @@ import { useEffect } from "react"; import { useSession } from "next-auth/react"; -import useTagStore from "@/store/tags"; import useLocalSettingsStore from "@/store/localSettings"; import { useUser } from "./store/user"; export default function useInitialData() { const { status, data } = useSession(); - // const { setCollections } = useCollectionStore(); - const { setTags } = useTagStore(); // const { setLinks } = useLinkStore(); const { data: user = [] } = useUser(); const { setSettings } = useLocalSettingsStore(); @@ -19,8 +16,6 @@ export default function useInitialData() { // Get the rest of the data useEffect(() => { if (user.id && (!process.env.NEXT_PUBLIC_STRIPE || user.username)) { - // setCollections(); - setTags(); // setLinks(); } }, [user]); diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index fe64928..adfa163 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -1,5 +1,4 @@ import useLinkStore from "@/store/links"; -import useTagStore from "@/store/tags"; import MainLayout from "@/layouts/MainLayout"; import { useEffect, useState } from "react"; import useLinks from "@/hooks/useLinks"; @@ -19,12 +18,13 @@ import MasonryView from "@/components/LinkViews/Layouts/MasonryView"; import getServerSideProps from "@/lib/client/getServerSideProps"; import { useTranslation } from "next-i18next"; import { useCollections } from "@/hooks/store/collections"; +import { useTags } from "@/hooks/store/tags"; export default function Dashboard() { const { t } = useTranslation(); const { data: collections = [] } = useCollections(); const { links } = useLinkStore(); - const { tags } = useTagStore(); + const { data: tags = [] } = useTags(); const [numberOfLinks, setNumberOfLinks] = useState(0); diff --git a/pages/tags/[id].tsx b/pages/tags/[id].tsx index 86df965..2467da6 100644 --- a/pages/tags/[id].tsx +++ b/pages/tags/[id].tsx @@ -2,7 +2,6 @@ import useLinkStore from "@/store/links"; import { useRouter } from "next/router"; import { FormEvent, useEffect, useState } from "react"; import MainLayout from "@/layouts/MainLayout"; -import useTagStore from "@/store/tags"; import { Sort, TagIncludingLinkCount, ViewMode } from "@/types/global"; import useLinks from "@/hooks/useLinks"; import { toast } from "react-hot-toast"; @@ -15,13 +14,16 @@ import MasonryView from "@/components/LinkViews/Layouts/MasonryView"; import { useTranslation } from "next-i18next"; import getServerSideProps from "@/lib/client/getServerSideProps"; import LinkListOptions from "@/components/LinkListOptions"; +import { useRemoveTag, useTags, useUpdateTag } from "@/hooks/store/tags"; export default function Index() { const { t } = useTranslation(); const router = useRouter(); const { links } = useLinkStore(); - const { tags, updateTag, removeTag } = useTagStore(); + const { data: tags = [] } = useTags(); + const updateTag = useUpdateTag(); + const removeTag = useRemoveTag(); const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); @@ -41,7 +43,7 @@ export default function Index() { useLinks({ tagId: Number(router.query.id), sort: sortBy }); useEffect(() => { - const tag = tags.find((e) => e.id === Number(router.query.id)); + const tag = tags.find((e: any) => e.id === Number(router.query.id)); if (tags.length > 0 && !tag?.id) { router.push("/dashboard"); @@ -72,21 +74,12 @@ export default function Index() { setSubmitLoader(true); - const load = toast.loading(t("applying_changes")); - - let response; - if (activeTag && newTagName) - response = await updateTag({ + await updateTag.mutateAsync({ ...activeTag, name: newTagName, }); - toast.dismiss(load); - - if (response?.ok) { - toast.success(t("tag_renamed")); - } else toast.error(response?.data as string); setSubmitLoader(false); setRenameTag(false); }; @@ -94,18 +87,13 @@ export default function Index() { const remove = async () => { setSubmitLoader(true); - const load = toast.loading(t("applying_changes")); + if (activeTag?.id) + await removeTag.mutateAsync(activeTag?.id, { + onSuccess: () => { + router.push("/links"); + }, + }); - let response; - - if (activeTag?.id) response = await removeTag(activeTag?.id); - - toast.dismiss(load); - - if (response?.ok) { - toast.success(t("tag_deleted")); - router.push("/links"); - } else toast.error(response?.data as string); setSubmitLoader(false); setRenameTag(false); }; diff --git a/store/links.ts b/store/links.ts index db9a1fc..9339b6e 100644 --- a/store/links.ts +++ b/store/links.ts @@ -3,7 +3,6 @@ import { ArchivedFormat, LinkIncludingShortenedCollectionAndTags, } from "@/types/global"; -import useTagStore from "./tags"; type ResponseObject = { ok: boolean; @@ -79,7 +78,6 @@ const useLinkStore = create()((set) => ({ set((state) => ({ links: [data.response, ...state.links], })); - useTagStore.getState().setTags(); } return { ok: response.ok, data: data.response }; @@ -154,7 +152,6 @@ const useLinkStore = create()((set) => ({ ...state.links, ], })); - useTagStore.getState().setTags(); } return { ok: response.ok, data: data.response }; @@ -209,7 +206,6 @@ const useLinkStore = create()((set) => ({ e.id === data.response.id ? data.response : e ), })); - useTagStore.getState().setTags(); } return { ok: response.ok, data: data.response }; @@ -243,7 +239,6 @@ const useLinkStore = create()((set) => ({ : e ), })); - useTagStore.getState().setTags(); } return { ok: response.ok, data: data.response }; @@ -262,7 +257,6 @@ const useLinkStore = create()((set) => ({ set((state) => ({ links: state.links.filter((e) => e.id !== linkId), })); - useTagStore.getState().setTags(); } return { ok: response.ok, data: data.response }; @@ -282,7 +276,6 @@ const useLinkStore = create()((set) => ({ set((state) => ({ links: state.links.filter((e) => !linkIds.includes(e.id as number)), })); - useTagStore.getState().setTags(); } return { ok: response.ok, data: data.response }; diff --git a/store/tags.ts b/store/tags.ts deleted file mode 100644 index 6778152..0000000 --- a/store/tags.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { create } from "zustand"; -import { TagIncludingLinkCount } from "@/types/global"; - -type ResponseObject = { - ok: boolean; - data: object | string; -}; - -type TagStore = { - tags: TagIncludingLinkCount[]; - setTags: () => void; - updateTag: (tag: TagIncludingLinkCount) => Promise; - removeTag: (tagId: number) => Promise; -}; - -const useTagStore = create()((set) => ({ - tags: [], - setTags: async () => { - const response = await fetch("/api/v1/tags"); - - const data = await response.json(); - - if (response.ok) set({ tags: data.response }); - }, - updateTag: async (tag) => { - const response = await fetch(`/api/v1/tags/${tag.id}`, { - body: JSON.stringify(tag), - headers: { - "Content-Type": "application/json", - }, - method: "PUT", - }); - - const data = await response.json(); - - if (response.ok) { - set((state) => ({ - tags: state.tags.map((e) => - e.id === data.response.id ? data.response : e - ), - })); - } - - return { ok: response.ok, data: data.response }; - }, - removeTag: async (tagId) => { - const response = await fetch(`/api/v1/tags/${tagId}`, { - method: "DELETE", - }); - - if (response.ok) { - set((state) => ({ - tags: state.tags.filter((e) => e.id !== tagId), - })); - } - - const data = await response.json(); - return { ok: response.ok, data: data.response }; - }, -})); - -export default useTagStore;