Skip to content

Commit

Permalink
Add note modifying functionality, dynamic route caching adjustments a…
Browse files Browse the repository at this point in the history
…nd map page(s) preparations (#4)

* add base note helpers for modifying relations and content

* add note-editor base

* add force dynamic rendering logic to notes page

* add note deletion functionality

* add better revalidation to dynamic pages

* add preparations for map pages
  • Loading branch information
bercivarga authored Mar 9, 2024
1 parent f42deba commit be319b7
Show file tree
Hide file tree
Showing 21 changed files with 874 additions and 28 deletions.
9 changes: 6 additions & 3 deletions app/(platform)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ export const metadata: Metadata = {

export default function Home() {
return (
<main>
<h1>Dashboard</h1>
<p>hi</p>
<main className="px-6 py-4">
<h2>Dashboard</h2>
<p>
Welcome to your dashboard. This is where you can start working on your
ideas. You can create new notes, view your recent notes, and more.
</p>
</main>
);
}
2 changes: 1 addition & 1 deletion app/(platform)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function RootLayout({
return (
<div className="flex h-screen divide-x">
<SideNav />
<div className="w-full p-4">{children}</div>
<div className="w-full">{children}</div>
</div>
);
}
45 changes: 45 additions & 0 deletions app/(platform)/map/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Metadata } from "next";

import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarTrigger,
} from "@/components/ui/menubar";

export const metadata: Metadata = {
title: "Map",
description: "Map",
};

export default function MapLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<div className="relative h-screen w-full">
{children}
<div className="absolute bottom-6 left-1/2 -translate-x-1/2">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>Save raster output</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarItem>2D</MenubarItem>
<MenubarSeparator />
<MenubarItem>3D</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions app/(platform)/map/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function MapPage() {
return (
<main>
<h2>Map</h2>
</main>
);
}
76 changes: 76 additions & 0 deletions app/(platform)/notes/[noteId]/note-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client";

import { TrashIcon } from "@radix-ui/react-icons";
import debounce from "lodash.debounce";
import { useRouter } from "next/navigation";
import { useState } from "react";

import { Button } from "@/components/ui/button";
import { deleteNote } from "@/helpers/notes/deleteNote";
import { getNote } from "@/helpers/notes/getNote";
import { updateNoteContent } from "@/helpers/notes/updateNoteContent";
import { updateNoteTitle } from "@/helpers/notes/updateNoteTitle";

type Props = {
note: NonNullable<Awaited<ReturnType<typeof getNote>>>; // I love TS
};

const debounceDelay = 500;
const debouncedUpdateNoteTitle = debounce(updateNoteTitle, debounceDelay);
const debouncedUpdateNoteContent = debounce(updateNoteContent, debounceDelay);

export default function NoteEditor({ note }: Props) {
const [title, setTitle] = useState(note.title);
const [content, setContent] = useState(note.content);

const router = useRouter();

async function handleDeleteNote() {
const deletedNote = await deleteNote(note.id);
if (!deletedNote) {
alert("Failed to delete note"); // TODO: better error handling with a toast
return;
}

router.push("/notes");
}

return (
<div className="flex h-full min-h-screen divide-x">
<div className="flex w-full flex-col gap-6 p-6">
<input
className="text-4xl font-bold outline-none"
value={title}
onChange={(e) => {
setTitle(e.target.value);
debouncedUpdateNoteTitle(note.id, {
title: e.target.value,
});
}}
/>
<textarea
className="h-96 resize-none text-lg outline-none"
value={content}
placeholder="Write your note here..."
onChange={(e) => {
setContent(e.target.value);
debouncedUpdateNoteContent(note.id, {
content: e.target.value,
});
}}
/>
</div>
<div className="flex w-80 flex-col items-stretch gap-4 bg-slate-100/50 p-6">
<span className="block text-sm text-slate-400">Note actions</span>
<Button
className="w-full justify-start"
variant={"destructive"}
onClick={handleDeleteNote}
>
<TrashIcon className="mr-3" />
Delete note
</Button>
</div>
</div>
);
}
34 changes: 26 additions & 8 deletions app/(platform)/notes/[noteId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";

import { getNote } from "@/helpers/notes/getNote";

export default async function EditNotePage({
params,
}: {
import NoteEditor from "./note-editor";

type Props = {
params: { noteId: string };
}) {
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { noteId } = params;
const note = await getNote(noteId);

if (!note) {
return {
title: "Note not found",
};
}

return {
title: `Edit note: ${note.title}`,
};
}

export default async function EditNotePage({ params }: Props) {
const { noteId } = params;
const note = await getNote(noteId);

if (!note || note.deleted) {
redirect("/notes");
}

return (
<main>
<h1>Edit note</h1>
<pre>
<code>{JSON.stringify(note, null, 2)}</code>
</pre>
<NoteEditor note={note} />
</main>
);
}

export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
2 changes: 1 addition & 1 deletion app/(platform)/notes/new/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function GET() {
const newNote = await prisma.note.create({
data: {
title: "New note",
content: "Start writing here...",
content: "",
authorId: dbUser.id,
},
});
Expand Down
26 changes: 18 additions & 8 deletions app/(platform)/notes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { CrossCircledIcon } from "@radix-ui/react-icons";
import { Edit2Icon, Edit3Icon } from "lucide-react";
import { Metadata } from "next";
import Link from "next/link";

import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
Expand All @@ -14,30 +12,40 @@ import {
} from "@/components/ui/table";
import { getAllNotes } from "@/helpers/notes/getAllNotes";

export const metadata: Metadata = {
title: "All notes",
};

export default async function AllNotesPage() {
const notes = await getAllNotes();

return (
<main>
<main className="px-6 py-4">
<Table>
<TableCaption>A list of your recent notes.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Title</TableHead>
<TableHead className="w-[200px]">Title</TableHead>
<TableHead className="w-[300px]">Snippet</TableHead>
<TableHead className="w-[200px]">Tags</TableHead>
<TableHead className="text-right">Updated</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableBody className="whitespace-nowrap">
{notes?.map((note) => (
<TableRow key={note.id}>
<TableCell>
<Link href={`/notes/${note.id}`}>{note.title}</Link>
<Link href={`/notes/${note.id}`}>
{note.title.length > 20
? note.title.slice(0, 20) + "..."
: note.title}
</Link>
</TableCell>
<TableCell>
<Link href={`/notes/${note.id}`}>
{note.content.slice(0, 50)}
{note.content.length > 50
? note.content.slice(0, 50) + "..."
: note.content}
</Link>
</TableCell>
<TableCell>
Expand All @@ -55,3 +63,5 @@ export default async function AllNotesPage() {
</main>
);
}
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
6 changes: 3 additions & 3 deletions app/(platform)/side-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import SearchBar from "./search-bar";

export default function SideNav() {
return (
<aside className="flex h-full w-64 flex-col justify-between bg-slate-100/50 p-6">
<div className="flex flex-col items-stretch gap-4">
<aside className="flex h-full w-80 flex-col justify-between bg-slate-100/50 p-6">
<nav className="flex flex-col items-stretch gap-4">
<SearchBar />
{navItems.map((item) => (
<Link key={item.href} href={item.href}>
Expand All @@ -19,7 +19,7 @@ export default function SideNav() {
</Button>
</Link>
))}
</div>
</nav>
<div>
<UserButton afterSignOutUrl="/" />
</div>
Expand Down
Loading

0 comments on commit be319b7

Please sign in to comment.