Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trash bin page and discard reset functionality #8

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading