Merge pull request #636 from bjoerndot/tags-in-public-collection

Tags in public collection
This commit is contained in:
Daniel 2024-11-02 20:55:16 -04:00 committed by GitHub
commit d12d12518e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 176 additions and 12 deletions

View File

@ -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<TagIncludingCount[]> => {
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 };

View File

@ -1,7 +1,14 @@
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
if (userId)
await prisma.tag.deleteMany({
where: {
ownerId: userId,
@ -28,6 +35,13 @@ export default async function getTags(userId: number) {
},
},
},
{
links: {
some: {
collectionId,
},
},
},
],
},
include: {

View File

@ -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<LinkRequestQuery, "tagId"> = {
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 });
}
}

View File

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

View File

@ -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<AccountSettings>
>({});
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<CollectionIncludingMembersAndLinkCount>();
useEffect(() => {
if (router.query.id) {
getPublicCollectionData(Number(router.query.id)).then((res) => {
@ -212,7 +239,56 @@ export default function PublicCollections() {
}
/>
</LinkListOptions>
{tags && tags[0] && (
<div className="flex gap-2 mt-2 mb-6 flex-wrap">
<button
className="max-w-full"
onClick={() => handleTagSelection(undefined)}
>
<div
className={`${
!router.query.q
? "bg-primary/20"
: "bg-neutral-content/20 hover:bg-neutral/20"
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 rounded-md h-8`}
>
<i className="text-primary bi-hash text-2xl drop-shadow"></i>
<p className="truncate pr-7">{t("all_links")}</p>
<div className="text-neutral drop-shadow text-xs">
{collection._count?.links}
</div>
</div>
</button>
{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 (
<button
className="max-w-full"
key={i}
onClick={() => handleTagSelection(e)}
>
<div
className={`${
active
? "bg-primary/20"
: "bg-neutral-content/20 hover:bg-neutral/20"
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 rounded-md h-8`}
>
<i className="bi-hash text-2xl text-primary drop-shadow"></i>
<p className="truncate pr-7">{e}</p>
<div className="drop-shadow text-neutral text-xs">
{tags.filter((t) => t.name === e)[0]._count.links}
</div>
</div>
</button>
);
})}
</div>
)}
<Links
links={
links?.map((e, i) => {