From b727f2b6d228479ea70d19a9fe959f3d9b1acae3 Mon Sep 17 00:00:00 2001 From: ashrafchowdury Date: Sun, 14 Jan 2024 12:24:04 +0800 Subject: [PATCH 1/2] feat: implemented pin document feature --- components/documents/document-card.tsx | 10 ++++ .../[teamId]/documents/[id]/pin-document.ts | 58 +++++++++++++++++++ pages/api/teams/[teamId]/documents/index.ts | 1 + pages/documents/[id]/index.tsx | 30 ++++++++++ pages/documents/index.tsx | 8 ++- .../20240113052250_pin_document/migration.sql | 2 + prisma/schema.prisma | 1 + 7 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 pages/api/teams/[teamId]/documents/[id]/pin-document.ts create mode 100644 prisma/migrations/20240113052250_pin_document/migration.sql diff --git a/components/documents/document-card.tsx b/components/documents/document-card.tsx index fadf8d488..170aabfc1 100644 --- a/components/documents/document-card.tsx +++ b/components/documents/document-card.tsx @@ -2,6 +2,7 @@ import { copyToClipboard, nFormatter, timeAgo } from "@/lib/utils"; import Link from "next/link"; import { DocumentWithLinksAndLinkCountAndViewCount } from "@/lib/types"; import Copy from "@/components/shared/icons/copy"; +import { Pin } from "lucide-react"; import BarChart from "@/components/shared/icons/bar-chart"; import Image from "next/image"; import NotionIcon from "@/components/shared/icons/notion"; @@ -85,7 +86,16 @@ export default function DocumentsCard({ /> + {document.pinned && ( +
+ + + Pinned + +
+ )} +

{timeAgo(document.createdAt)}

diff --git a/pages/api/teams/[teamId]/documents/[id]/pin-document.ts b/pages/api/teams/[teamId]/documents/[id]/pin-document.ts new file mode 100644 index 000000000..5db3f3f47 --- /dev/null +++ b/pages/api/teams/[teamId]/documents/[id]/pin-document.ts @@ -0,0 +1,58 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { getServerSession } from "next-auth/next"; +import prisma from "@/lib/prisma"; +import { authOptions } from "@/pages/api/auth/[...nextauth]"; +import { CustomUser } from "@/lib/types"; +import { getTeamWithUsersAndDocument } from "@/lib/team/helper"; +import { errorhandler } from "@/lib/errorHandler"; + +export default async function handle( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === "POST") { + // GET /api/teams/:teamId/documents/:id/pin-document + const session = await getServerSession(req, res, authOptions); + if (!session) { + return res.status(401).end("Unauthorized"); + } + + const { teamId, id: docId } = req.query as { teamId: string; id: string }; + + const userId = (session.user as CustomUser).id; + + try { + await getTeamWithUsersAndDocument({ + teamId, + userId, + docId, + checkOwner: true, + options: { + select: { + id: true, + ownerId: true, + }, + }, + }); + + await prisma.document.update({ + where: { + id: docId, + }, + data: { + pinned: req.body.pinned, + }, + }); + + return res.status(200).json({ + message: req.body.pinned ? "Pinned document!" : "Unpinned document!", + }); + } catch (error) { + errorhandler(error, res); + } + } else { + // We only allow POST requests + res.setHeader("Allow", ["POST"]); + return res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/teams/[teamId]/documents/index.ts b/pages/api/teams/[teamId]/documents/index.ts index d7e8447ce..b5ba8b829 100644 --- a/pages/api/teams/[teamId]/documents/index.ts +++ b/pages/api/teams/[teamId]/documents/index.ts @@ -108,6 +108,7 @@ export default async function handle( type: type, ownerId: (session.user as CustomUser).id, teamId: teamId, + pinned: false, links: { create: {}, }, diff --git a/pages/documents/[id]/index.tsx b/pages/documents/[id]/index.tsx index bb23ab6aa..968e14d16 100644 --- a/pages/documents/[id]/index.tsx +++ b/pages/documents/[id]/index.tsx @@ -197,6 +197,31 @@ export default function DocumentPage() { } }; + const pinDocument = async (document: Document) => { + const response = await fetch( + `/api/teams/${teamInfo?.currentTeam?.id}/documents/${ + prismaDocument!.id + }/pin-document`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + pinned: !document.pinned, + }), + }, + ); + + if (response.ok) { + const { message } = await response.json(); + toast.success(message); + } else { + const { message } = await response.json(); + toast.error(message); + } + }; + if (error && error.status === 404) { return ; } @@ -321,6 +346,11 @@ export default function DocumentPage() { ) ) : null} + pinDocument(prismaDocument)} + > + {prismaDocument.pinned ? "Unpin document" : "Pin document"} + diff --git a/pages/documents/index.tsx b/pages/documents/index.tsx index c97b20f21..284e0f981 100644 --- a/pages/documents/index.tsx +++ b/pages/documents/index.tsx @@ -40,9 +40,11 @@ export default function Documents() { {/* Documents list */}
    {documents - ? documents.map((document) => { - return ; - }) + ? documents + .sort((a, b) => (a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1)) + .map((document) => { + return ; + }) : Array.from({ length: 3 }).map((_, i) => (
  • Date: Mon, 19 Feb 2024 14:07:39 +0800 Subject: [PATCH 2/2] enhance: enhanced the feature usaibility --- components/documents/document-card.tsx | 67 +++++++++++++++++++++----- pages/documents/[id]/index.tsx | 32 ------------ 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/components/documents/document-card.tsx b/components/documents/document-card.tsx index 4ced2a2c5..6b91b1b78 100644 --- a/components/documents/document-card.tsx +++ b/components/documents/document-card.tsx @@ -17,13 +17,12 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { TrashIcon, MoreVertical } from "lucide-react"; +import { TrashIcon, MoreVertical, Pin, PinOff } from "lucide-react"; import { mutate } from "swr"; import { useCopyToClipboard } from "@/lib/utils/use-copy-to-clipboard"; import Check from "../shared/icons/check"; import Copy from "../shared/icons/copy"; import { useEffect, useRef, useState } from "react"; -import { Pin } from "lucide-react"; type DocumentsCardProps = { document: DocumentWithLinksAndLinkCountAndViewCount; @@ -146,6 +145,43 @@ export default function DocumentsCard({ } }; + const pinDocument = async ( + document: DocumentWithLinksAndLinkCountAndViewCount, + ) => { + const response = await fetch( + `/api/teams/${teamInfo?.currentTeam?.id}/documents/${prismaDocument.id}/pin-document`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + pinned: !document.pinned, + }), + }, + ); + + if (response.ok) { + const { message } = await response.json(); + // update document from the cache + mutate(`/api/teams/${teamInfo?.currentTeam?.id}/documents`, null, { + populateCache: (_, docs) => { + return docs.map((doc: DocumentWithLinksAndLinkCountAndViewCount) => { + if (doc.id === prismaDocument.id) { + return { ...doc, pinned: !doc.pinned }; + } + return doc; + }); + }, + revalidate: false, + }); + toast.success(message); + } else { + const { message } = await response.json(); + toast.error(message); + } + }; + return (
  • @@ -189,7 +225,7 @@ export default function DocumentsCard({ )}
    - {document.pinned && ( + {prismaDocument.pinned && (
    @@ -256,22 +292,29 @@ export default function DocumentsCard({ - + Actions - - handleButtonClick(event, prismaDocument.id)} - className="text-destructive focus:bg-destructive focus:text-destructive-foreground duration-200" - > - {isFirstClick ? ( - "Really delete?" + pinDocument(prismaDocument)}> + {prismaDocument.pinned ? ( + <> + + Unpin document + ) : ( <> - Delete document + + Pin document )} + handleButtonClick(event, prismaDocument.id)} + className="text-destructive focus:bg-destructive focus:text-destructive-foreground duration-200" + > + + {isFirstClick ? "Really delete?" : "Delete document"} +
    diff --git a/pages/documents/[id]/index.tsx b/pages/documents/[id]/index.tsx index 041f8c01b..b7c7b2a15 100644 --- a/pages/documents/[id]/index.tsx +++ b/pages/documents/[id]/index.tsx @@ -225,31 +225,6 @@ export default function DocumentPage() { }); }; - const pinDocument = async (document: Document) => { - const response = await fetch( - `/api/teams/${teamInfo?.currentTeam?.id}/documents/${ - prismaDocument!.id - }/pin-document`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - pinned: !document.pinned, - }), - }, - ); - - if (response.ok) { - const { message } = await response.json(); - toast.success(message); - } else { - const { message } = await response.json(); - toast.error(message); - } - }; - if (error && error.status === 404) { return ; } @@ -391,13 +366,6 @@ export default function DocumentPage() { - pinDocument(prismaDocument)} - > - {prismaDocument.pinned - ? "Unpin document" - : "Pin document"} -