diff --git a/hooks/store/publicTags.tsx b/hooks/store/publicTags.tsx new file mode 100644 index 0000000..aedeb75 --- /dev/null +++ b/hooks/store/publicTags.tsx @@ -0,0 +1,30 @@ +import { Tag } from "@prisma/client"; +import { useQuery, UseQueryResult } from "@tanstack/react-query"; +import { useSession } from "next-auth/react"; +import { useRouter } from "next/router"; + +type TagIncludingCount = Tag & { _count: { links: number } }; + +const usePublicTags = (): UseQueryResult => { + const { status } = useSession(); + + const router = useRouter(); + + return useQuery({ + queryKey: ["tags"], + queryFn: async () => { + const response = await fetch( + "/api/v1/public/collections/tags" + + "?collectionId=" + + router.query.id || "" + ); + if (!response.ok) throw new Error("Failed to fetch tags."); + + const data = await response.json(); + return data.response; + }, + enabled: status === "authenticated", + }); +}; + +export { usePublicTags }; diff --git a/lib/api/controllers/tags/getTags.ts b/lib/api/controllers/tags/getTags.ts index 85a8d17..90c9f30 100644 --- a/lib/api/controllers/tags/getTags.ts +++ b/lib/api/controllers/tags/getTags.ts @@ -1,15 +1,22 @@ import { prisma } from "@/lib/api/db"; -export default async function getTags(userId: number) { +export default async function getTags({ + userId, + collectionId, +}: { + userId?: number; + collectionId?: number; +}) { // Remove empty tags - await prisma.tag.deleteMany({ - where: { - ownerId: userId, - links: { - none: {}, + if (userId) + await prisma.tag.deleteMany({ + where: { + ownerId: userId, + links: { + none: {}, + }, }, - }, - }); + }); const tags = await prisma.tag.findMany({ where: { @@ -28,6 +35,13 @@ export default async function getTags(userId: number) { }, }, }, + { + links: { + some: { + collectionId, + }, + }, + }, ], }, include: { diff --git a/pages/api/v1/public/collections/tags/index.ts b/pages/api/v1/public/collections/tags/index.ts new file mode 100644 index 0000000..43e693c --- /dev/null +++ b/pages/api/v1/public/collections/tags/index.ts @@ -0,0 +1,42 @@ +import getTags from "@/lib/api/controllers/tags/getTags"; +import { prisma } from "@/lib/api/db"; +import { LinkRequestQuery } from "@/types/global"; +import type { NextApiRequest, NextApiResponse } from "next"; + +export default async function collections( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === "GET") { + // Convert the type of the request query to "LinkRequestQuery" + const convertedData: Omit = { + sort: Number(req.query.sort as string), + collectionId: req.query.collectionId + ? Number(req.query.collectionId as string) + : undefined, + }; + + if (!convertedData.collectionId) { + return res + .status(400) + .json({ response: "Please choose a valid collection." }); + } + + const collection = await prisma.collection.findFirst({ + where: { + id: convertedData.collectionId, + isPublic: true, + }, + }); + + if (!collection) { + return res.status(404).json({ response: "Collection not found." }); + } + + const tags = await getTags({ + collectionId: collection.id, + }); + + return res.status(tags.status).json({ response: tags.response }); + } +} diff --git a/pages/api/v1/tags/index.ts b/pages/api/v1/tags/index.ts index 2376ef9..3135001 100644 --- a/pages/api/v1/tags/index.ts +++ b/pages/api/v1/tags/index.ts @@ -7,7 +7,9 @@ export default async function tags(req: NextApiRequest, res: NextApiResponse) { if (!user) return; if (req.method === "GET") { - const tags = await getTags(user.id); + const tags = await getTags({ + userId: user.id, + }); return res.status(tags.status).json({ response: tags.response }); } } diff --git a/pages/public/collections/[id].tsx b/pages/public/collections/[id].tsx index d49660f..95adcd6 100644 --- a/pages/public/collections/[id].tsx +++ b/pages/public/collections/[id].tsx @@ -22,6 +22,7 @@ import getServerSideProps from "@/lib/client/getServerSideProps"; import LinkListOptions from "@/components/LinkListOptions"; import { usePublicLinks } from "@/hooks/store/publicLinks"; import Links from "@/components/LinkViews/Links"; +import { usePublicTags } from "@/hooks/store/publicTags"; export default function PublicCollections() { const { t } = useTranslation(); @@ -34,6 +35,32 @@ export default function PublicCollections() { Partial >({}); + const handleTagSelection = (tag: string | undefined) => { + if (tag) { + Object.keys(searchFilter).forEach( + (v) => + (searchFilter[ + v as keyof { + name: boolean; + url: boolean; + description: boolean; + tags: boolean; + textContent: boolean; + } + ] = false) + ); + searchFilter.tags = true; + return router.push( + "/public/collections/" + + router.query.id + + "?q=" + + encodeURIComponent(tag || "") + ); + } else { + return router.push("/public/collections/" + router.query.id); + } + }; + const [searchFilter, setSearchFilter] = useState({ name: true, url: true, @@ -46,6 +73,8 @@ export default function PublicCollections() { Number(localStorage.getItem("sortBy")) ?? Sort.DateNewestFirst ); + const { data: tags } = usePublicTags(); + const { links, data } = usePublicLinks({ sort: sortBy, searchQueryString: router.query.q @@ -57,10 +86,8 @@ export default function PublicCollections() { searchByTextContent: searchFilter.textContent, searchByTags: searchFilter.tags, }); - const [collection, setCollection] = useState(); - useEffect(() => { if (router.query.id) { getPublicCollectionData(Number(router.query.id)).then((res) => { @@ -212,7 +239,56 @@ export default function PublicCollections() { } /> - + {tags && tags[0] && ( +
+ + {tags + .map((t) => t.name) + .filter((item, pos, self) => self.indexOf(item) === pos) + .sort((a, b) => a.localeCompare(b)) + .map((e, i) => { + const active = router.query.q === e; + return ( + + ); + })} +
+ )} {