refactor tags store

This commit is contained in:
daniel31x13 2024-08-01 17:23:51 -04:00
parent e889509697
commit da8dc83b8f
8 changed files with 108 additions and 107 deletions

View File

@ -1,8 +1,8 @@
import useTagStore from "@/store/tags";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import CreatableSelect from "react-select/creatable"; import CreatableSelect from "react-select/creatable";
import { styles } from "./styles"; import { styles } from "./styles";
import { Options } from "./types"; import { Options } from "./types";
import { useTags } from "@/hooks/store/tags";
type Props = { type Props = {
onChange: any; onChange: any;
@ -13,12 +13,12 @@ type Props = {
}; };
export default function TagSelection({ onChange, defaultValue }: Props) { export default function TagSelection({ onChange, defaultValue }: Props) {
const { tags } = useTagStore(); const { data: tags = [] } = useTags();
const [options, setOptions] = useState<Options[]>([]); const [options, setOptions] = useState<Options[]>([]);
useEffect(() => { useEffect(() => {
const formatedCollections = tags.map((e) => { const formatedCollections = tags.map((e: any) => {
return { value: e.id, label: e.name }; return { value: e.id, label: e.name };
}); });

View File

@ -1,4 +1,3 @@
import useTagStore from "@/store/tags";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -7,6 +6,7 @@ import SidebarHighlightLink from "@/components/SidebarHighlightLink";
import CollectionListing from "@/components/CollectionListing"; import CollectionListing from "@/components/CollectionListing";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections"; import { useCollections } from "@/hooks/store/collections";
import { useTags } from "@/hooks/store/tags";
export default function Sidebar({ className }: { className?: string }) { export default function Sidebar({ className }: { className?: string }) {
const { t } = useTranslation(); const { t } = useTranslation();
@ -24,7 +24,7 @@ export default function Sidebar({ className }: { className?: string }) {
const { data: collections = [] } = useCollections(); const { data: collections = [] } = useCollections();
const { tags } = useTagStore(); const { data: tags = [] } = useTags();
const [active, setActive] = useState(""); const [active, setActive] = useState("");
const router = useRouter(); const router = useRouter();
@ -130,8 +130,8 @@ export default function Sidebar({ className }: { className?: string }) {
<Disclosure.Panel className="flex flex-col gap-1"> <Disclosure.Panel className="flex flex-col gap-1">
{tags[0] ? ( {tags[0] ? (
tags tags
.sort((a, b) => a.name.localeCompare(b.name)) .sort((a: any, b: any) => a.name.localeCompare(b.name))
.map((e, i) => { .map((e: any, i: any) => {
return ( return (
<Link key={i} href={`/tags/${e.id}`}> <Link key={i} href={`/tags/${e.id}`}>
<div <div

87
hooks/store/tags.tsx Normal file
View File

@ -0,0 +1,87 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { useTranslation } from "next-i18next";
import { TagIncludingLinkCount } from "@/types/global";
const useTags = () => {
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 };

View File

@ -1,13 +1,10 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import useTagStore from "@/store/tags";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
import { useUser } from "./store/user"; import { useUser } from "./store/user";
export default function useInitialData() { export default function useInitialData() {
const { status, data } = useSession(); const { status, data } = useSession();
// const { setCollections } = useCollectionStore();
const { setTags } = useTagStore();
// const { setLinks } = useLinkStore(); // const { setLinks } = useLinkStore();
const { data: user = [] } = useUser(); const { data: user = [] } = useUser();
const { setSettings } = useLocalSettingsStore(); const { setSettings } = useLocalSettingsStore();
@ -19,8 +16,6 @@ export default function useInitialData() {
// Get the rest of the data // Get the rest of the data
useEffect(() => { useEffect(() => {
if (user.id && (!process.env.NEXT_PUBLIC_STRIPE || user.username)) { if (user.id && (!process.env.NEXT_PUBLIC_STRIPE || user.username)) {
// setCollections();
setTags();
// setLinks(); // setLinks();
} }
}, [user]); }, [user]);

View File

@ -1,5 +1,4 @@
import useLinkStore from "@/store/links"; import useLinkStore from "@/store/links";
import useTagStore from "@/store/tags";
import MainLayout from "@/layouts/MainLayout"; import MainLayout from "@/layouts/MainLayout";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import useLinks from "@/hooks/useLinks"; import useLinks from "@/hooks/useLinks";
@ -19,12 +18,13 @@ import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import getServerSideProps from "@/lib/client/getServerSideProps"; import getServerSideProps from "@/lib/client/getServerSideProps";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections"; import { useCollections } from "@/hooks/store/collections";
import { useTags } from "@/hooks/store/tags";
export default function Dashboard() { export default function Dashboard() {
const { t } = useTranslation(); const { t } = useTranslation();
const { data: collections = [] } = useCollections(); const { data: collections = [] } = useCollections();
const { links } = useLinkStore(); const { links } = useLinkStore();
const { tags } = useTagStore(); const { data: tags = [] } = useTags();
const [numberOfLinks, setNumberOfLinks] = useState(0); const [numberOfLinks, setNumberOfLinks] = useState(0);

View File

@ -2,7 +2,6 @@ import useLinkStore from "@/store/links";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { FormEvent, useEffect, useState } from "react"; import { FormEvent, useEffect, useState } from "react";
import MainLayout from "@/layouts/MainLayout"; import MainLayout from "@/layouts/MainLayout";
import useTagStore from "@/store/tags";
import { Sort, TagIncludingLinkCount, ViewMode } from "@/types/global"; import { Sort, TagIncludingLinkCount, ViewMode } from "@/types/global";
import useLinks from "@/hooks/useLinks"; import useLinks from "@/hooks/useLinks";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
@ -15,13 +14,16 @@ import MasonryView from "@/components/LinkViews/Layouts/MasonryView";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import getServerSideProps from "@/lib/client/getServerSideProps"; import getServerSideProps from "@/lib/client/getServerSideProps";
import LinkListOptions from "@/components/LinkListOptions"; import LinkListOptions from "@/components/LinkListOptions";
import { useRemoveTag, useTags, useUpdateTag } from "@/hooks/store/tags";
export default function Index() { export default function Index() {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const { links } = useLinkStore(); const { links } = useLinkStore();
const { tags, updateTag, removeTag } = useTagStore(); const { data: tags = [] } = useTags();
const updateTag = useUpdateTag();
const removeTag = useRemoveTag();
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst); const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
@ -41,7 +43,7 @@ export default function Index() {
useLinks({ tagId: Number(router.query.id), sort: sortBy }); useLinks({ tagId: Number(router.query.id), sort: sortBy });
useEffect(() => { 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) { if (tags.length > 0 && !tag?.id) {
router.push("/dashboard"); router.push("/dashboard");
@ -72,21 +74,12 @@ export default function Index() {
setSubmitLoader(true); setSubmitLoader(true);
const load = toast.loading(t("applying_changes"));
let response;
if (activeTag && newTagName) if (activeTag && newTagName)
response = await updateTag({ await updateTag.mutateAsync({
...activeTag, ...activeTag,
name: newTagName, name: newTagName,
}); });
toast.dismiss(load);
if (response?.ok) {
toast.success(t("tag_renamed"));
} else toast.error(response?.data as string);
setSubmitLoader(false); setSubmitLoader(false);
setRenameTag(false); setRenameTag(false);
}; };
@ -94,18 +87,13 @@ export default function Index() {
const remove = async () => { const remove = async () => {
setSubmitLoader(true); 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); setSubmitLoader(false);
setRenameTag(false); setRenameTag(false);
}; };

View File

@ -3,7 +3,6 @@ import {
ArchivedFormat, ArchivedFormat,
LinkIncludingShortenedCollectionAndTags, LinkIncludingShortenedCollectionAndTags,
} from "@/types/global"; } from "@/types/global";
import useTagStore from "./tags";
type ResponseObject = { type ResponseObject = {
ok: boolean; ok: boolean;
@ -79,7 +78,6 @@ const useLinkStore = create<LinkStore>()((set) => ({
set((state) => ({ set((state) => ({
links: [data.response, ...state.links], links: [data.response, ...state.links],
})); }));
useTagStore.getState().setTags();
} }
return { ok: response.ok, data: data.response }; return { ok: response.ok, data: data.response };
@ -154,7 +152,6 @@ const useLinkStore = create<LinkStore>()((set) => ({
...state.links, ...state.links,
], ],
})); }));
useTagStore.getState().setTags();
} }
return { ok: response.ok, data: data.response }; return { ok: response.ok, data: data.response };
@ -209,7 +206,6 @@ const useLinkStore = create<LinkStore>()((set) => ({
e.id === data.response.id ? data.response : e e.id === data.response.id ? data.response : e
), ),
})); }));
useTagStore.getState().setTags();
} }
return { ok: response.ok, data: data.response }; return { ok: response.ok, data: data.response };
@ -243,7 +239,6 @@ const useLinkStore = create<LinkStore>()((set) => ({
: e : e
), ),
})); }));
useTagStore.getState().setTags();
} }
return { ok: response.ok, data: data.response }; return { ok: response.ok, data: data.response };
@ -262,7 +257,6 @@ const useLinkStore = create<LinkStore>()((set) => ({
set((state) => ({ set((state) => ({
links: state.links.filter((e) => e.id !== linkId), links: state.links.filter((e) => e.id !== linkId),
})); }));
useTagStore.getState().setTags();
} }
return { ok: response.ok, data: data.response }; return { ok: response.ok, data: data.response };
@ -282,7 +276,6 @@ const useLinkStore = create<LinkStore>()((set) => ({
set((state) => ({ set((state) => ({
links: state.links.filter((e) => !linkIds.includes(e.id as number)), links: state.links.filter((e) => !linkIds.includes(e.id as number)),
})); }));
useTagStore.getState().setTags();
} }
return { ok: response.ok, data: data.response }; return { ok: response.ok, data: data.response };

View File

@ -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<ResponseObject>;
removeTag: (tagId: number) => Promise<ResponseObject>;
};
const useTagStore = create<TagStore>()((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;