From 05c5bdf63cf7b1647f8716e66e0fee18c91d5e40 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Tue, 30 Jul 2024 14:57:09 -0400 Subject: [PATCH] refactor collections store --- components/CollectionListing.tsx | 8 +- .../InputSelect/CollectionSelection.tsx | 6 +- components/LinkViews/LinkCard.tsx | 6 +- components/LinkViews/LinkList.tsx | 6 +- components/LinkViews/LinkMasonry.tsx | 5 +- .../ModalContent/DeleteCollectionModal.tsx | 25 +- .../ModalContent/EditCollectionModal.tsx | 20 +- .../EditCollectionSharingModal.tsx | 21 +- .../ModalContent/NewCollectionModal.tsx | 21 +- components/ModalContent/NewLinkModal.tsx | 6 +- components/ModalContent/UploadFileModal.tsx | 5 +- components/ReadableView.tsx | 5 +- components/Sidebar.tsx | 6 +- components/ui/Loader.tsx | 272 ++++++++++++++++++ hooks/store/collections.tsx | 131 +++++++++ hooks/useCollectivePermissions.ts | 5 +- hooks/useInitialData.tsx | 5 +- hooks/usePermissions.tsx | 5 +- package.json | 2 + pages/_app.tsx | 140 ++++----- pages/collections/[id].tsx | 5 +- pages/collections/index.tsx | 5 +- pages/dashboard.tsx | 5 +- pages/public/collections/[id].tsx | 5 +- store/collections.ts | 94 ------ yarn.lock | 24 ++ 26 files changed, 585 insertions(+), 253 deletions(-) create mode 100644 components/ui/Loader.tsx create mode 100644 hooks/store/collections.tsx delete mode 100644 store/collections.ts diff --git a/components/CollectionListing.tsx b/components/CollectionListing.tsx index fbe2f7b..d3b433e 100644 --- a/components/CollectionListing.tsx +++ b/components/CollectionListing.tsx @@ -9,7 +9,6 @@ import Tree, { TreeSourcePosition, TreeDestinationPosition, } from "@atlaskit/tree"; -import useCollectionStore from "@/store/collections"; import { Collection } from "@prisma/client"; import Link from "next/link"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; @@ -17,6 +16,7 @@ import { useRouter } from "next/router"; import useAccountStore from "@/store/account"; import toast from "react-hot-toast"; import { useTranslation } from "next-i18next"; +import { useCollections, useUpdateCollection } from "@/hooks/store/collections"; interface ExtendedTreeItem extends TreeItem { data: Collection; @@ -24,7 +24,9 @@ interface ExtendedTreeItem extends TreeItem { const CollectionListing = () => { const { t } = useTranslation(); - const { collections, updateCollection } = useCollectionStore(); + const updateCollection = useUpdateCollection(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const { account, updateAccount } = useAccountStore(); const router = useRouter(); @@ -151,7 +153,7 @@ const CollectionListing = () => { const updatedCollectionOrder = [...account.collectionOrder]; if (source.parentId !== destination.parentId) { - await updateCollection({ + await updateCollection.mutateAsync({ ...movedCollection, parentId: destination.parentId && destination.parentId !== "root" diff --git a/components/InputSelect/CollectionSelection.tsx b/components/InputSelect/CollectionSelection.tsx index 99b999e..8a98b9e 100644 --- a/components/InputSelect/CollectionSelection.tsx +++ b/components/InputSelect/CollectionSelection.tsx @@ -1,10 +1,10 @@ -import useCollectionStore from "@/store/collections"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { styles } from "./styles"; import { Options } from "./types"; import CreatableSelect from "react-select/creatable"; import Select from "react-select"; +import { useCollections } from "@/hooks/store/collections"; type Props = { onChange: any; @@ -24,7 +24,9 @@ export default function CollectionSelection({ showDefaultValue = true, creatable = true, }: Props) { - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); + const router = useRouter(); const [options, setOptions] = useState([]); diff --git a/components/LinkViews/LinkCard.tsx b/components/LinkViews/LinkCard.tsx index 70f553c..20597b7 100644 --- a/components/LinkViews/LinkCard.tsx +++ b/components/LinkViews/LinkCard.tsx @@ -5,7 +5,6 @@ import { } from "@/types/global"; import { useEffect, useRef, useState } from "react"; import useLinkStore from "@/store/links"; -import useCollectionStore from "@/store/collections"; import unescapeString from "@/lib/client/unescapeString"; import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; @@ -21,6 +20,7 @@ import usePermissions from "@/hooks/usePermissions"; import toast from "react-hot-toast"; import LinkTypeBadge from "./LinkComponents/LinkTypeBadge"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -34,7 +34,9 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { const { t } = useTranslation(); const viewMode = localStorage.getItem("viewMode") || "card"; - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); + const { account } = useAccountStore(); const { links, getLink, setSelectedLinks, selectedLinks } = useLinkStore(); diff --git a/components/LinkViews/LinkList.tsx b/components/LinkViews/LinkList.tsx index 723c47e..936d857 100644 --- a/components/LinkViews/LinkList.tsx +++ b/components/LinkViews/LinkList.tsx @@ -4,7 +4,6 @@ import { } from "@/types/global"; import { useEffect, useState } from "react"; import useLinkStore from "@/store/links"; -import useCollectionStore from "@/store/collections"; import unescapeString from "@/lib/client/unescapeString"; import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; @@ -17,6 +16,7 @@ import usePermissions from "@/hooks/usePermissions"; import toast from "react-hot-toast"; import LinkTypeBadge from "./LinkComponents/LinkTypeBadge"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -33,7 +33,9 @@ export default function LinkCardCompact({ }: Props) { const { t } = useTranslation(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); + const { account } = useAccountStore(); const { links, setSelectedLinks, selectedLinks } = useLinkStore(); diff --git a/components/LinkViews/LinkMasonry.tsx b/components/LinkViews/LinkMasonry.tsx index 022ac93..c3c2451 100644 --- a/components/LinkViews/LinkMasonry.tsx +++ b/components/LinkViews/LinkMasonry.tsx @@ -5,7 +5,6 @@ import { } from "@/types/global"; import { useEffect, useRef, useState } from "react"; import useLinkStore from "@/store/links"; -import useCollectionStore from "@/store/collections"; import unescapeString from "@/lib/client/unescapeString"; import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; @@ -21,6 +20,7 @@ import usePermissions from "@/hooks/usePermissions"; import toast from "react-hot-toast"; import LinkTypeBadge from "./LinkComponents/LinkTypeBadge"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -33,7 +33,8 @@ type Props = { export default function LinkMasonry({ link, flipDropdown, editMode }: Props) { const { t } = useTranslation(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const { account } = useAccountStore(); const { links, getLink, setSelectedLinks, selectedLinks } = useLinkStore(); diff --git a/components/ModalContent/DeleteCollectionModal.tsx b/components/ModalContent/DeleteCollectionModal.tsx index 504d3a3..907f1c0 100644 --- a/components/ModalContent/DeleteCollectionModal.tsx +++ b/components/ModalContent/DeleteCollectionModal.tsx @@ -1,13 +1,12 @@ import React, { useEffect, useState } from "react"; import TextInput from "@/components/TextInput"; -import useCollectionStore from "@/store/collections"; -import toast from "react-hot-toast"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import { useRouter } from "next/router"; import usePermissions from "@/hooks/usePermissions"; import Modal from "../Modal"; import Button from "../ui/Button"; import { useTranslation } from "next-i18next"; +import { useDeleteCollection } from "@/hooks/store/collections"; type Props = { onClose: Function; @@ -22,7 +21,6 @@ export default function DeleteCollectionModal({ const [collection, setCollection] = useState(activeCollection); const [submitLoader, setSubmitLoader] = useState(false); - const { removeCollection } = useCollectionStore(); const router = useRouter(); const [inputField, setInputField] = useState(""); const permissions = usePermissions(collection.id as number); @@ -31,6 +29,8 @@ export default function DeleteCollectionModal({ setCollection(activeCollection); }, []); + const deleteCollection = useDeleteCollection(); + const submit = async () => { if (permissions === true && collection.name !== inputField) return; if (!submitLoader) { @@ -39,19 +39,12 @@ export default function DeleteCollectionModal({ setSubmitLoader(true); - const load = toast.loading(t("deleting_collection")); - - let response = await removeCollection(collection.id as number); - - toast.dismiss(load); - - if (response.ok) { - toast.success(t("deleted")); - onClose(); - router.push("/collections"); - } else { - toast.error(response.data as string); - } + deleteCollection.mutateAsync(collection.id as number, { + onSuccess: () => { + onClose(); + router.push("/collections"); + }, + }); setSubmitLoader(false); } diff --git a/components/ModalContent/EditCollectionModal.tsx b/components/ModalContent/EditCollectionModal.tsx index b2105f5..3009122 100644 --- a/components/ModalContent/EditCollectionModal.tsx +++ b/components/ModalContent/EditCollectionModal.tsx @@ -1,11 +1,10 @@ import React, { useState } from "react"; import TextInput from "@/components/TextInput"; -import useCollectionStore from "@/store/collections"; -import toast from "react-hot-toast"; import { HexColorPicker } from "react-colorful"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import Modal from "../Modal"; import { useTranslation } from "next-i18next"; +import { useUpdateCollection } from "@/hooks/store/collections"; type Props = { onClose: Function; @@ -21,7 +20,7 @@ export default function EditCollectionModal({ useState(activeCollection); const [submitLoader, setSubmitLoader] = useState(false); - const { updateCollection } = useCollectionStore(); + const updateCollection = useUpdateCollection(); const submit = async () => { if (!submitLoader) { @@ -30,16 +29,11 @@ export default function EditCollectionModal({ setSubmitLoader(true); - const load = toast.loading(t("updating_collection")); - - let response = await updateCollection(collection as any); - - toast.dismiss(load); - - if (response.ok) { - toast.success(t("updated")); - onClose(); - } else toast.error(response.data as string); + await updateCollection.mutateAsync(collection, { + onSuccess: () => { + onClose(); + }, + }); setSubmitLoader(false); } diff --git a/components/ModalContent/EditCollectionSharingModal.tsx b/components/ModalContent/EditCollectionSharingModal.tsx index 9a9f5fa..10831d7 100644 --- a/components/ModalContent/EditCollectionSharingModal.tsx +++ b/components/ModalContent/EditCollectionSharingModal.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from "react"; import TextInput from "@/components/TextInput"; -import useCollectionStore from "@/store/collections"; import toast from "react-hot-toast"; import { CollectionIncludingMembersAndLinkCount, Member } from "@/types/global"; import getPublicUserData from "@/lib/client/getPublicUserData"; @@ -11,6 +10,7 @@ import addMemberToCollection from "@/lib/client/addMemberToCollection"; import Modal from "../Modal"; import { dropdownTriggerer } from "@/lib/client/utils"; import { useTranslation } from "next-i18next"; +import { useUpdateCollection } from "@/hooks/store/collections"; type Props = { onClose: Function; @@ -27,7 +27,7 @@ export default function EditCollectionSharingModal({ useState(activeCollection); const [submitLoader, setSubmitLoader] = useState(false); - const { updateCollection } = useCollectionStore(); + const updateCollection = useUpdateCollection(); const submit = async () => { if (!submitLoader) { @@ -36,18 +36,11 @@ export default function EditCollectionSharingModal({ setSubmitLoader(true); - const load = toast.loading(t("updating")); - - let response; - - response = await updateCollection(collection as any); - - toast.dismiss(load); - - if (response.ok) { - toast.success(t("updated")); - onClose(); - } else toast.error(response.data as string); + await updateCollection.mutateAsync(collection, { + onSuccess: () => { + onClose(); + }, + }); setSubmitLoader(false); } diff --git a/components/ModalContent/NewCollectionModal.tsx b/components/ModalContent/NewCollectionModal.tsx index 980bb44..e1ab424 100644 --- a/components/ModalContent/NewCollectionModal.tsx +++ b/components/ModalContent/NewCollectionModal.tsx @@ -1,7 +1,5 @@ import React, { useEffect, useState } from "react"; import TextInput from "@/components/TextInput"; -import useCollectionStore from "@/store/collections"; -import toast from "react-hot-toast"; import { HexColorPicker } from "react-colorful"; import { Collection } from "@prisma/client"; import Modal from "../Modal"; @@ -9,6 +7,7 @@ import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import useAccountStore from "@/store/account"; import { useSession } from "next-auth/react"; import { useTranslation } from "next-i18next"; +import { useCreateCollection } from "@/hooks/store/collections"; type Props = { onClose: Function; @@ -33,7 +32,8 @@ export default function NewCollectionModal({ onClose, parent }: Props) { }, []); const [submitLoader, setSubmitLoader] = useState(false); - const { addCollection } = useCollectionStore(); + + const createCollection = useCreateCollection(); const submit = async () => { if (submitLoader) return; @@ -41,18 +41,11 @@ export default function NewCollectionModal({ onClose, parent }: Props) { setSubmitLoader(true); - const load = toast.loading(t("creating")); - - let response = await addCollection(collection as any); - toast.dismiss(load); - - if (response.ok) { - toast.success(t("created_success")); - if (response.data) { - setAccount(data?.user.id as number); + await createCollection.mutateAsync(collection, { + onSuccess: () => { onClose(); - } - } else toast.error(response.data as string); + }, + }); setSubmitLoader(false); }; diff --git a/components/ModalContent/NewLinkModal.tsx b/components/ModalContent/NewLinkModal.tsx index 0284687..605d03c 100644 --- a/components/ModalContent/NewLinkModal.tsx +++ b/components/ModalContent/NewLinkModal.tsx @@ -1,10 +1,8 @@ import React, { useEffect, useState } from "react"; -import { Toaster } from "react-hot-toast"; import CollectionSelection from "@/components/InputSelect/CollectionSelection"; import TagSelection from "@/components/InputSelect/TagSelection"; import TextInput from "@/components/TextInput"; import unescapeString from "@/lib/client/unescapeString"; -import useCollectionStore from "@/store/collections"; import useLinkStore from "@/store/links"; import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; import { useSession } from "next-auth/react"; @@ -12,6 +10,7 @@ import { useRouter } from "next/router"; import toast from "react-hot-toast"; import Modal from "../Modal"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; type Props = { onClose: Function; @@ -44,7 +43,8 @@ export default function NewLinkModal({ onClose }: Props) { const [submitLoader, setSubmitLoader] = useState(false); const [optionsExpanded, setOptionsExpanded] = useState(false); const router = useRouter(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const setCollection = (e: any) => { if (e?.__isNew__) e.value = null; diff --git a/components/ModalContent/UploadFileModal.tsx b/components/ModalContent/UploadFileModal.tsx index 2480431..c5c3b5e 100644 --- a/components/ModalContent/UploadFileModal.tsx +++ b/components/ModalContent/UploadFileModal.tsx @@ -3,7 +3,6 @@ import CollectionSelection from "@/components/InputSelect/CollectionSelection"; import TagSelection from "@/components/InputSelect/TagSelection"; import TextInput from "@/components/TextInput"; import unescapeString from "@/lib/client/unescapeString"; -import useCollectionStore from "@/store/collections"; import useLinkStore from "@/store/links"; import { LinkIncludingShortenedCollectionAndTags, @@ -14,6 +13,7 @@ import { useRouter } from "next/router"; import toast from "react-hot-toast"; import Modal from "../Modal"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; type Props = { onClose: Function; @@ -49,7 +49,8 @@ export default function UploadFileModal({ onClose }: Props) { const [submitLoader, setSubmitLoader] = useState(false); const [optionsExpanded, setOptionsExpanded] = useState(false); const router = useRouter(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const setCollection = (e: any) => { if (e?.__isNew__) e.value = null; diff --git a/components/ReadableView.tsx b/components/ReadableView.tsx index b600236..e10461b 100644 --- a/components/ReadableView.tsx +++ b/components/ReadableView.tsx @@ -14,8 +14,8 @@ import Link from "next/link"; import { useRouter } from "next/router"; import React, { useEffect, useMemo, useState } from "react"; import LinkActions from "./LinkViews/LinkComponents/LinkActions"; -import useCollectionStore from "@/store/collections"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; type LinkContent = { title: string; @@ -46,7 +46,8 @@ export default function ReadableView({ link }: Props) { const router = useRouter(); const { getLink } = useLinkStore(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const collection = useMemo(() => { return collections.find( diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 11a0f62..20cfd0b 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -1,4 +1,3 @@ -import useCollectionStore from "@/store/collections"; import useTagStore from "@/store/tags"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -7,6 +6,7 @@ import { Disclosure, Transition } from "@headlessui/react"; import SidebarHighlightLink from "@/components/SidebarHighlightLink"; import CollectionListing from "@/components/CollectionListing"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; export default function Sidebar({ className }: { className?: string }) { const { t } = useTranslation(); @@ -22,7 +22,9 @@ export default function Sidebar({ className }: { className?: string }) { } ); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); + const { tags } = useTagStore(); const [active, setActive] = useState(""); diff --git a/components/ui/Loader.tsx b/components/ui/Loader.tsx new file mode 100644 index 0000000..ac5f570 --- /dev/null +++ b/components/ui/Loader.tsx @@ -0,0 +1,272 @@ +import React from "react"; + +type Props = { + className?: string; + color: string; + size: string; +}; + +const Loader = (props: Props) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Loader; diff --git a/hooks/store/collections.tsx b/hooks/store/collections.tsx new file mode 100644 index 0000000..46109dc --- /dev/null +++ b/hooks/store/collections.tsx @@ -0,0 +1,131 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; +import { useTranslation } from "next-i18next"; +import toast from "react-hot-toast"; + +const useCollections = () => { + return useQuery({ + queryKey: ["collections"], + queryFn: async (): Promise<{ + response: CollectionIncludingMembersAndLinkCount[]; + }> => { + const response = await fetch("/api/v1/collections"); + const data = await response.json(); + return data; + }, + }); +}; + +const useCreateCollection = () => { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (data: any) => { + const load = toast.loading(t("creating")); + + const response = await fetch("/api/v1/collections", { + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + }, + method: "POST", + }); + + toast.dismiss(load); + + return response.json(); + }, + onSuccess: (data) => { + toast.success(t("created")); + return queryClient.setQueryData(["collections"], (oldData: any) => { + return { + response: [...oldData.response, data.response], + }; + }); + }, + onError: (error) => { + toast.error(error.message); + }, + }); +}; + +const useUpdateCollection = () => { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (data: any) => { + const load = toast.loading(t("updating_collection")); + + const response = await fetch(`/api/v1/collections/${data.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + toast.dismiss(load); + + return response.json(); + }, + onSuccess: (data) => { + { + toast.success(t("updated")); + return queryClient.setQueryData(["collections"], (oldData: any) => { + return { + response: oldData.response.map((collection: any) => + collection.id === data.response.id ? data.response : collection + ), + }; + }); + } + }, + onError: (error) => { + toast.error(error.message); + }, + }); +}; + +const useDeleteCollection = () => { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (id: number) => { + const load = toast.loading(t("deleting_collection")); + + const response = await fetch(`/api/v1/collections/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + toast.dismiss(load); + + return response.json(); + }, + onSuccess: (data) => { + toast.success(t("deleted")); + return queryClient.setQueryData(["collections"], (oldData: any) => { + return { + response: oldData.response.filter( + (collection: any) => collection.id !== data.response.id + ), + }; + }); + }, + onError: (error) => { + toast.error(error.message); + }, + }); +}; + +export { + useCollections, + useCreateCollection, + useUpdateCollection, + useDeleteCollection, +}; diff --git a/hooks/useCollectivePermissions.ts b/hooks/useCollectivePermissions.ts index b1e3b7c..992befe 100644 --- a/hooks/useCollectivePermissions.ts +++ b/hooks/useCollectivePermissions.ts @@ -1,10 +1,11 @@ import useAccountStore from "@/store/account"; -import useCollectionStore from "@/store/collections"; import { Member } from "@/types/global"; import { useEffect, useState } from "react"; +import { useCollections } from "./store/collections"; export default function useCollectivePermissions(collectionIds: number[]) { - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const { account } = useAccountStore(); diff --git a/hooks/useInitialData.tsx b/hooks/useInitialData.tsx index 4b0dd17..2da9d9e 100644 --- a/hooks/useInitialData.tsx +++ b/hooks/useInitialData.tsx @@ -1,4 +1,3 @@ -import useCollectionStore from "@/store/collections"; import { useEffect } from "react"; import { useSession } from "next-auth/react"; import useTagStore from "@/store/tags"; @@ -7,7 +6,7 @@ import useLocalSettingsStore from "@/store/localSettings"; export default function useInitialData() { const { status, data } = useSession(); - const { setCollections } = useCollectionStore(); + // const { setCollections } = useCollectionStore(); const { setTags } = useTagStore(); // const { setLinks } = useLinkStore(); const { account, setAccount } = useAccountStore(); @@ -24,7 +23,7 @@ export default function useInitialData() { // Get the rest of the data useEffect(() => { if (account.id && (!process.env.NEXT_PUBLIC_STRIPE || account.username)) { - setCollections(); + // setCollections(); setTags(); // setLinks(); } diff --git a/hooks/usePermissions.tsx b/hooks/usePermissions.tsx index 746897b..bf21c18 100644 --- a/hooks/usePermissions.tsx +++ b/hooks/usePermissions.tsx @@ -1,10 +1,11 @@ import useAccountStore from "@/store/account"; -import useCollectionStore from "@/store/collections"; import { Member } from "@/types/global"; import { useEffect, useState } from "react"; +import { useCollections } from "./store/collections"; export default function usePermissions(collectionId: number) { - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const { account } = useAccountStore(); diff --git a/package.json b/package.json index 1c428cf..f766af5 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "@mozilla/readability": "^0.4.4", "@prisma/client": "^4.16.2", "@stripe/stripe-js": "^1.54.1", + "@tanstack/react-query": "^5.51.15", + "@tanstack/react-query-devtools": "^5.51.15", "@types/crypto-js": "^4.1.1", "@types/formidable": "^3.4.5", "@types/node": "^20.10.4", diff --git a/pages/_app.tsx b/pages/_app.tsx index 0cb79ff..45f3657 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -11,7 +11,10 @@ import { Session } from "next-auth"; import { isPWA } from "@/lib/client/utils"; // import useInitialData from "@/hooks/useInitialData"; import { appWithTranslation } from "next-i18next"; -import nextI18nextConfig from "../next-i18next.config"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const queryClient = new QueryClient(); function App({ Component, @@ -29,72 +32,75 @@ function App({ }, []); return ( - - - Linkwarden - - - - - - - - - {/* */} - - {(t) => ( - - {({ icon, message }) => ( -
- {icon} - {message} - {t.type !== "loading" && ( - - )} -
- )} -
- )} -
- - {/*
*/} -
-
+ + + + Linkwarden + + + + + + + + + {/* */} + + {(t) => ( + + {({ icon, message }) => ( +
+ {icon} + {message} + {t.type !== "loading" && ( + + )} +
+ )} +
+ )} +
+ + {/*
*/} +
+
+ +
); } diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index bd24a95..ccbcae3 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -1,4 +1,3 @@ -import useCollectionStore from "@/store/collections"; import useLinkStore from "@/store/links"; import { CollectionIncludingMembersAndLinkCount, @@ -26,6 +25,7 @@ import MasonryView from "@/components/LinkViews/Layouts/MasonryView"; import getServerSideProps from "@/lib/client/getServerSideProps"; import { useTranslation } from "next-i18next"; import LinkListOptions from "@/components/LinkListOptions"; +import { useCollections } from "@/hooks/store/collections"; export default function Index() { const { t } = useTranslation(); @@ -34,7 +34,8 @@ export default function Index() { const router = useRouter(); const { links } = useLinkStore(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); diff --git a/pages/collections/index.tsx b/pages/collections/index.tsx index 642e3dd..3314dfb 100644 --- a/pages/collections/index.tsx +++ b/pages/collections/index.tsx @@ -1,4 +1,3 @@ -import useCollectionStore from "@/store/collections"; import CollectionCard from "@/components/CollectionCard"; import { useState } from "react"; import MainLayout from "@/layouts/MainLayout"; @@ -10,10 +9,12 @@ import NewCollectionModal from "@/components/ModalContent/NewCollectionModal"; import PageHeader from "@/components/PageHeader"; import getServerSideProps from "@/lib/client/getServerSideProps"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; export default function Collections() { const { t } = useTranslation(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); const [sortedCollections, setSortedCollections] = useState(collections); diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index 3d1fdb6..40ee933 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -1,5 +1,4 @@ import useLinkStore from "@/store/links"; -import useCollectionStore from "@/store/collections"; import useTagStore from "@/store/tags"; import MainLayout from "@/layouts/MainLayout"; import { useEffect, useState } from "react"; @@ -19,10 +18,12 @@ import { dropdownTriggerer } from "@/lib/client/utils"; import MasonryView from "@/components/LinkViews/Layouts/MasonryView"; import getServerSideProps from "@/lib/client/getServerSideProps"; import { useTranslation } from "next-i18next"; +import { useCollections } from "@/hooks/store/collections"; export default function Dashboard() { const { t } = useTranslation(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const { links } = useLinkStore(); const { tags } = useTagStore(); diff --git a/pages/public/collections/[id].tsx b/pages/public/collections/[id].tsx index 47e074f..faa4950 100644 --- a/pages/public/collections/[id].tsx +++ b/pages/public/collections/[id].tsx @@ -23,8 +23,8 @@ import ListView from "@/components/LinkViews/Layouts/ListView"; import MasonryView from "@/components/LinkViews/Layouts/MasonryView"; import { useTranslation } from "next-i18next"; import getServerSideProps from "@/lib/client/getServerSideProps"; -import useCollectionStore from "@/store/collections"; import LinkListOptions from "@/components/LinkListOptions"; +import { useCollections } from "@/hooks/store/collections"; export default function PublicCollections() { const { t } = useTranslation(); @@ -32,7 +32,8 @@ export default function PublicCollections() { const { settings } = useLocalSettingsStore(); - const { collections } = useCollectionStore(); + const { data: { response: collections } = { response: [] } } = + useCollections(); const router = useRouter(); diff --git a/store/collections.ts b/store/collections.ts deleted file mode 100644 index 466b652..0000000 --- a/store/collections.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { create } from "zustand"; -import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; -import useTagStore from "./tags"; - -type ResponseObject = { - ok: boolean; - data: object | string; -}; - -type CollectionStore = { - collections: CollectionIncludingMembersAndLinkCount[]; - setCollections: () => void; - addCollection: ( - body: CollectionIncludingMembersAndLinkCount - ) => Promise; - updateCollection: ( - collection: CollectionIncludingMembersAndLinkCount - ) => Promise; - removeCollection: (collectionId: number) => Promise; -}; - -const useCollectionStore = create()((set) => ({ - collections: [], - setCollections: async () => { - const response = await fetch("/api/v1/collections"); - - const data = await response.json(); - - if (response.ok) set({ collections: data.response }); - }, - addCollection: async (body) => { - const response = await fetch("/api/v1/collections", { - body: JSON.stringify(body), - headers: { - "Content-Type": "application/json", - }, - method: "POST", - }); - - const data = await response.json(); - - if (response.ok) - set((state) => ({ - collections: [...state.collections, data.response], - })); - - return { ok: response.ok, data: data.response }; - }, - updateCollection: async (collection) => { - const response = await fetch(`/api/v1/collections/${collection.id}`, { - body: JSON.stringify(collection), - headers: { - "Content-Type": "application/json", - }, - method: "PUT", - }); - - const data = await response.json(); - - if (response.ok) - set((state) => ({ - collections: state.collections.map((e) => - e.id === data.response.id ? data.response : e - ), - })); - - return { ok: response.ok, data: data.response }; - }, - removeCollection: async (collectionId) => { - const response = await fetch(`/api/v1/collections/${collectionId}`, { - headers: { - "Content-Type": "application/json", - }, - method: "DELETE", - }); - - const data = await response.json(); - - if (response.ok) { - set((state) => ({ - collections: state.collections.filter( - (collection) => - collection.id !== collectionId && - collection.parentId !== collectionId - ), - })); - useTagStore.getState().setTags(); - } - - return { ok: response.ok, data: data.response }; - }, -})); - -export default useCollectionStore; diff --git a/yarn.lock b/yarn.lock index 943096e..174222e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1903,6 +1903,30 @@ dependencies: tslib "^2.4.0" +"@tanstack/query-core@5.51.15": + version "5.51.15" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.51.15.tgz#7aee6a2d5d3f64de3e54096607233b1132dc6afd" + integrity sha512-xyobHDJ0yhPE3+UkSQ2/4X1fLSg7ICJI5J1JyU9yf7F3deQfEwSImCDrB1WSRrauJkMtXW7YIEcC0oA6ZZWt5A== + +"@tanstack/query-devtools@5.51.15": + version "5.51.15" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.51.15.tgz#81c5c28231adc4b95fe4a5e1004020fdca5ea447" + integrity sha512-1oSCl+PsCa/aBCGVM2ZdcQLuQ0QYmKXJJB264twEMVM1M0n5CI40trtywORPF+wLGuZNIZzkKL7j/98mOLAIag== + +"@tanstack/react-query-devtools@^5.51.15": + version "5.51.15" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.51.15.tgz#5c4d21305fd25c35dc88bd280304f77a45554fc2" + integrity sha512-bvGvJoncjZ3irEofoFevptj5BPkDpQrp2+dZhtFqPUZXRT6MAKPmOqtSmZPfacLR5jQLpqw/7d3Zxr173z7WDA== + dependencies: + "@tanstack/query-devtools" "5.51.15" + +"@tanstack/react-query@^5.51.15": + version "5.51.15" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.51.15.tgz#059bb2966f828263adb355de81410d107e22b5bc" + integrity sha512-UgFg23SrdIYrmfTSxAUn9g+J64VQy11pb9/EefoY/u2+zWuNMeqEOnvpJhf52XQy0yztQoyM9p6x8PFyTNaxXg== + dependencies: + "@tanstack/query-core" "5.51.15" + "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"