Skip to content

Commit

Permalink
add trash page and adjust data fetching to fit trash requirements
Browse files Browse the repository at this point in the history
  • Loading branch information
bercivarga committed Mar 11, 2024
1 parent ba6c05c commit e007c25
Show file tree
Hide file tree
Showing 12 changed files with 330 additions and 20 deletions.
6 changes: 4 additions & 2 deletions app/(platform)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<div className="flex h-screen divide-x">
<div className="flex h-screen max-h-screen divide-x">
<SideNav />
<div className="w-full">{children}</div>
<div className="h-full max-h-full w-full overflow-y-scroll">
{children}
</div>
</div>
);
}
1 change: 0 additions & 1 deletion app/(platform)/map/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Metadata } from "next";

export const metadata: Metadata = {
title: "Map of your notes",
description: "Explore your notes and their relationships.",
};

export default function MapLayout({
Expand Down
17 changes: 13 additions & 4 deletions app/(platform)/notes/notes-pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import {
Pagination,
PaginationContent,
Expand All @@ -11,11 +13,13 @@ import {
type Props = {
currentPage?: number;
totalPages?: number;
type?: "notes" | "trash";
};

export default function NotesPagination({
currentPage = 1,
totalPages = 1,
type = "notes",
}: Props) {
if (currentPage === 1 && totalPages === 1) {
return null;
Expand All @@ -24,16 +28,21 @@ export default function NotesPagination({
const isThereAPreviousPage = currentPage > 1;
const isThereANextPage = totalPages > currentPage;

const path = type === "notes" ? "/notes" : "/notes/trash";

const prevPageHref = `${path}?page=${currentPage - 1}`;
const nextPageHref = `${path}?page=${currentPage + 1}`;

return (
<Pagination>
<PaginationContent>
{isThereAPreviousPage && (
<>
<PaginationItem>
<PaginationPrevious href={`/notes?page=${currentPage - 1}`} />
<PaginationPrevious href={prevPageHref} />
</PaginationItem>
<PaginationItem>
<PaginationLink href={`/notes?page=${currentPage - 1}`}>
<PaginationLink href={prevPageHref}>
{currentPage - 1}
</PaginationLink>
</PaginationItem>
Expand All @@ -50,12 +59,12 @@ export default function NotesPagination({
{isThereANextPage && (
<>
<PaginationItem>
<PaginationLink href={`/notes?page=${currentPage + 1}`}>
<PaginationLink href={nextPageHref}>
{currentPage + 1}
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href={`/notes?page=${currentPage + 1}`} />
<PaginationNext href={nextPageHref} />
</PaginationItem>
</>
)}
Expand Down
27 changes: 23 additions & 4 deletions app/(platform)/notes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PlusCircledIcon, TrashIcon } from "@radix-ui/react-icons";
import { Metadata } from "next";
import Link from "next/link";

import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
Expand Down Expand Up @@ -34,15 +36,32 @@ export default async function AllNotesPage({ searchParams }: Props) {

return (
<main className="px-6 py-4">
<Table>
<div className="flex w-full items-center justify-between ">
<h5 className="font-bold">Notes</h5>
<div className="flex items-center gap-4">
<Link href="/notes/trash">
<Button variant="outline" className="gap-2">
<TrashIcon />
Trash bin
</Button>
</Link>
<Link href="/notes/new">
<Button className="gap-2">
<PlusCircledIcon />
Create
</Button>
</Link>
</div>
</div>
<Table className="mt-4">
<TableCaption>
A list of your recent notes ({notes?.length ?? 0} of {count})
A list of your recent notes (showing {notes?.length ?? 0} of {count})
</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[200px]">Title</TableHead>
<TableHead className="w-[300px]">Snippet</TableHead>
<TableHead className="w-[200px]">Tags</TableHead>
<TableHead className="w-[300px]">Tags</TableHead>
<TableHead className="text-right">Updated</TableHead>
</TableRow>
</TableHeader>
Expand All @@ -63,7 +82,7 @@ export default async function AllNotesPage({ searchParams }: Props) {
: note.content}
</Link>
</TableCell>
<TableCell>
<TableCell className="max-w-[300px] overflow-hidden overflow-ellipsis">
{note.tags.map((tag) => tag.name).join(", ")}
</TableCell>
<TableCell className="text-right">
Expand Down
87 changes: 87 additions & 0 deletions app/(platform)/notes/trash/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Metadata } from "next";

import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getAllNotes } from "@/helpers/notes/getAllNotes";

import NotesPagination from "../notes-pagination";
import RestoreButton from "./restore-button";

type Props = {
params: {};
searchParams: { page?: string };
};

export const metadata: Metadata = {
title: "Trash bin",
};

export default async function TrashPage({ searchParams }: Props) {
const currentPage = searchParams.page ? parseInt(searchParams.page) : 1;

const { count, notes, totalPages } =
(await getAllNotes({
page: currentPage,
paginate: true,
results: "deleted",
})) ?? {};

return (
<main className="px-6 py-4">
<div>
<h5 className="font-bold">Trash bin</h5>
</div>
<Table className="mt-4">
<TableCaption>
A list of your discarded notes (showing {notes?.length ?? 0} of{" "}
{count})
</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Action</TableHead>
<TableHead className="w-[200px]">Title</TableHead>
<TableHead className="w-[300px]">Snippet</TableHead>
<TableHead className="text-right">Updated</TableHead>
</TableRow>
</TableHeader>
<TableBody className="whitespace-nowrap">
{notes?.map((note) => (
<TableRow key={note.id}>
<TableCell>
<RestoreButton noteId={note.id} />
</TableCell>
<TableCell>
{note.title.length > 20
? note.title.slice(0, 20) + "..."
: note.title}
</TableCell>
<TableCell>
{note.content.length > 50
? note.content.slice(0, 50) + "..."
: note.content}
</TableCell>
<TableCell className="text-right">
{new Date(note.updatedAt).toLocaleString()}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<NotesPagination
type="trash"
currentPage={currentPage}
totalPages={totalPages}
/>
</main>
);
}

export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
26 changes: 26 additions & 0 deletions app/(platform)/notes/trash/restore-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { ResetIcon } from "@radix-ui/react-icons";
import { useRouter } from "next/navigation";

import { Button } from "@/components/ui/button";
import { restoreNote } from "@/helpers/notes/restoreNote";

type Props = {
noteId: string;
};

export default function RestoreButton({ noteId }: Props) {
const router = useRouter();

async function handleRestore() {
await restoreNote(noteId);
router.refresh();
}

return (
<Button variant="outline" size="icon" onClick={handleRestore}>
<ResetIcon />
</Button>
);
}
33 changes: 33 additions & 0 deletions components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";

/* eslint-disable react/prop-types */
/* eslint-disable react/no-unknown-property */

import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import * as React from "react";

import { cn } from "@/lib/utils";

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md bg-slate-900 px-3 py-1.5 text-xs text-slate-50 dark:bg-slate-50 dark:text-slate-900",
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
22 changes: 22 additions & 0 deletions helpers/notes/deleteNote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,33 @@ export async function deleteNote(noteId: string) {
data: { deleted: true },
});

// Need to disconnect relations after note is deleted
const notesWithThisNoteAsRelated = await prisma.note.findMany({
where: {
OR: [
{ relatedNotes: { some: { id: noteId } } },
{ relatedTo: { some: { id: noteId } } },
],
},
});

for (const note of notesWithThisNoteAsRelated) {
await prisma.note.update({
where: { id: note.id },
data: {
relatedNotes: {
disconnect: { id: noteId },
},
},
});
}

return note;
} catch (error) {
return null;
} finally {
revalidatePath("/notes");
revalidatePath("/notes/trash");
revalidatePath(`/notes/${noteId}`, "page");
}
}
31 changes: 24 additions & 7 deletions helpers/notes/getAllNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import { redirect } from "next/navigation";

import { prisma } from "@/lib/db";

const PAGE_SIZE = 12;
const PAGE_SIZE = 24;

type GetAllNotesOptions = {
page?: number;
paginate?: boolean;
results?: "all" | "deleted" | "active";
};

export async function getAllNotes({
page,
paginate = false,
}: {
page?: number;
paginate?: boolean;
} = {}) {
results = "active",
}: GetAllNotesOptions = {}) {
const { userId } = auth();

if (!userId) {
Expand All @@ -28,12 +32,25 @@ export async function getAllNotes({
return null;
}

let deletedOption = undefined;

switch (results) {
case "all":
break;
case "deleted":
deletedOption = true;
break;
case "active":
deletedOption = false;
break;
}

const transaction = await prisma.$transaction([
prisma.note.count({
where: { authorId: dbUser.id, deleted: false },
where: { authorId: dbUser.id, deleted: deletedOption },
}),
prisma.note.findMany({
where: { authorId: dbUser.id, deleted: false },
where: { authorId: dbUser.id, deleted: deletedOption },
include: { tags: true, relatedNotes: true, relatedTo: true },
orderBy: { updatedAt: "desc" },
take: paginate ? PAGE_SIZE : undefined,
Expand Down
Loading

0 comments on commit e007c25

Please sign in to comment.