refactor collections store

This commit is contained in:
daniel31x13 2024-07-30 14:57:09 -04:00
parent cd82083e09
commit 05c5bdf63c
26 changed files with 585 additions and 253 deletions

View File

@ -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"

View File

@ -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<Options[]>([]);

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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<CollectionIncludingMembersAndLinkCount>(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);
}

View File

@ -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<CollectionIncludingMembersAndLinkCount>(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);
}

View File

@ -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<CollectionIncludingMembersAndLinkCount>(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);
}

View File

@ -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);
};

View File

@ -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;

View File

@ -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;

View File

@ -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(

View File

@ -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("");

272
components/ui/Loader.tsx Normal file
View File

@ -0,0 +1,272 @@
import React from "react";
type Props = {
className?: string;
color: string;
size: string;
};
const Loader = (props: Props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid"
width={props.size}
height={props.size}
className={props.className}
style={{
shapeRendering: "auto",
display: "block",
background: "rgba(255, 255, 255, 0)",
}}
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g>
<g transform="rotate(0 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.9166666666666666s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(30 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.8333333333333334s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(60 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.75s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(90 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.6666666666666666s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(120 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.5833333333333334s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(150 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.5s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(180 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.4166666666666667s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(210 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.3333333333333333s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(240 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.25s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(270 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.16666666666666666s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(300 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="-0.08333333333333333s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g transform="rotate(330 50 50)">
<rect
fill={props.color}
height="12"
width="6"
ry="1.8"
rx="1.8"
y="24"
x="47"
>
<animate
repeatCount="indefinite"
begin="0s"
dur="1s"
keyTimes="0;1"
values="1;0"
attributeName="opacity"
></animate>
</rect>
</g>
<g></g>
</g>
</svg>
);
};
export default Loader;

131
hooks/store/collections.tsx Normal file
View File

@ -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,
};

View File

@ -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();

View File

@ -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();
}

View File

@ -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();

View File

@ -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",

View File

@ -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 (
<SessionProvider
session={pageProps.session}
refetchOnWindowFocus={false}
basePath="/api/v1/auth"
>
<Head>
<title>Linkwarden</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
</Head>
<AuthRedirect>
{/* <GetData> */}
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-800 dark:text-white",
}}
>
{(t) => (
<ToastBar toast={t}>
{({ icon, message }) => (
<div
className="flex flex-row"
data-testid="toast-message-container"
data-type={t.type}
>
{icon}
<span data-testid="toast-message">{message}</span>
{t.type !== "loading" && (
<button
className="btn btn-xs outline-none btn-circle btn-ghost"
data-testid="close-toast-button"
onClick={() => toast.dismiss(t.id)}
>
<i className="bi bi-x"></i>
</button>
)}
</div>
)}
</ToastBar>
)}
</Toaster>
<Component {...pageProps} />
{/* </GetData> */}
</AuthRedirect>
</SessionProvider>
<QueryClientProvider client={queryClient}>
<SessionProvider
session={pageProps.session}
refetchOnWindowFocus={false}
basePath="/api/v1/auth"
>
<Head>
<title>Linkwarden</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
</Head>
<AuthRedirect>
{/* <GetData> */}
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-800 dark:text-white",
}}
>
{(t) => (
<ToastBar toast={t}>
{({ icon, message }) => (
<div
className="flex flex-row"
data-testid="toast-message-container"
data-type={t.type}
>
{icon}
<span data-testid="toast-message">{message}</span>
{t.type !== "loading" && (
<button
className="btn btn-xs outline-none btn-circle btn-ghost"
data-testid="close-toast-button"
onClick={() => toast.dismiss(t.id)}
>
<i className="bi bi-x"></i>
</button>
)}
</div>
)}
</ToastBar>
)}
</Toaster>
<Component {...pageProps} />
{/* </GetData> */}
</AuthRedirect>
</SessionProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}

View File

@ -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>(Sort.DateNewestFirst);

View File

@ -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>(Sort.DateNewestFirst);
const [sortedCollections, setSortedCollections] = useState(collections);

View File

@ -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();

View File

@ -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();

View File

@ -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<ResponseObject>;
updateCollection: (
collection: CollectionIncludingMembersAndLinkCount
) => Promise<ResponseObject>;
removeCollection: (collectionId: number) => Promise<ResponseObject>;
};
const useCollectionStore = create<CollectionStore>()((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;

View File

@ -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"