Skip to content

Commit

Permalink
Add node creation flow and setup for future pages (#3)
Browse files Browse the repository at this point in the history
* add sidebar nav items, add command implementation for suggestions

* add note creation logic and redirects

* add all notes table, adjust schema and add helpers to fetch and delete note(s)
  • Loading branch information
bercivarga authored Mar 9, 2024
1 parent 7cd6bde commit f42deba
Show file tree
Hide file tree
Showing 18 changed files with 1,247 additions and 23 deletions.
File renamed without changes.
14 changes: 14 additions & 0 deletions app/(platform)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import SideNav from "./side-nav";

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<div className="flex h-screen divide-x">
<SideNav />
<div className="w-full p-4">{children}</div>
</div>
);
}
31 changes: 31 additions & 0 deletions app/(platform)/navItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
CardStackIcon,
GridIcon,
HomeIcon,
Pencil2Icon,
} from "@radix-ui/react-icons";

const navItems = [
{
label: "Dashboard",
href: "/dashboard",
icon: HomeIcon,
},
{
label: "New note",
href: "/notes/new",
icon: Pencil2Icon,
},
{
label: "All notes",
href: "/notes",
icon: CardStackIcon,
},
{
label: "Map",
href: "/map",
icon: GridIcon,
},
];

export default navItems;
25 changes: 25 additions & 0 deletions app/(platform)/notes/[noteId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { redirect } from "next/navigation";

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

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

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

return (
<main>
<h1>Edit note</h1>
<pre>
<code>{JSON.stringify(note, null, 2)}</code>
</pre>
</main>
);
}
51 changes: 51 additions & 0 deletions app/(platform)/notes/new/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { auth } from "@clerk/nextjs";
import { redirect } from "next/navigation";
import { NextResponse } from "next/server";

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

// Handles the creation of a new note, fills the note up with placeholder data, then redirects the user
export async function GET() {
const { userId } = auth();

if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}

let newNoteId: string;

try {
const dbUser = await prisma.user.findUnique({
where: { clerkId: userId },
});

if (!dbUser) {
return new NextResponse("User in own database not found", {
status: 404,
});
}

const newNote = await prisma.note.create({
data: {
title: "New note",
content: "Start writing here...",
authorId: dbUser.id,
},
});

const { id } = newNote;

newNoteId = id;
} catch (error) {
return new NextResponse(
`Error creating note: ${(error as Error).message}`,
{
status: 500,
}
);
}

// This is necessary to be done here because Next's app directory routing throws an error internally
// that makes it impossible to use it in a try-catch block. Pretty annoying but this is the cleanest solution without a workaround.
redirect(`/notes/${newNoteId}`);
}
57 changes: 57 additions & 0 deletions app/(platform)/notes/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { CrossCircledIcon } from "@radix-ui/react-icons";
import { Edit2Icon, Edit3Icon } from "lucide-react";
import Link from "next/link";

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

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

return (
<main>
<Table>
<TableCaption>A list of your recent notes.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Title</TableHead>
<TableHead className="w-[300px]">Snippet</TableHead>
<TableHead className="w-[200px]">Tags</TableHead>
<TableHead className="text-right">Updated</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{notes?.map((note) => (
<TableRow key={note.id}>
<TableCell>
<Link href={`/notes/${note.id}`}>{note.title}</Link>
</TableCell>
<TableCell>
<Link href={`/notes/${note.id}`}>
{note.content.slice(0, 50)}
</Link>
</TableCell>
<TableCell>
{note.tags.map((tag) => tag.name).join(", ")}
</TableCell>
<TableCell className="text-right">
<Link href={`/notes/${note.id}`}>
{new Date(note.updatedAt).toLocaleString()}
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</main>
);
}
79 changes: 79 additions & 0 deletions app/(platform)/search-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client";

import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
KeyboardEvent as ReactKeyboardEvent,
useEffect,
useState,
} from "react";

import { Button } from "@/components/ui/button";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";

import navItems from "./navItems";

export default function SearchBar() {
const [showSearchCommand, setShowSearchCommand] = useState(false);

const router = useRouter();

useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setShowSearchCommand((open) => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);

return (
<>
<Button
className="justify-between text-slate-400 hover:text-slate-800"
variant="outline"
onClick={() => setShowSearchCommand(true)}
>
<div className="flex items-center gap-3">
<MagnifyingGlassIcon />
<span>Search</span>
</div>
<span>⌘K</span>
</Button>
<CommandDialog
open={showSearchCommand}
onOpenChange={setShowSearchCommand}
>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
{navItems.map((item) => (
<Link href={item.href} key={item.href}>
<CommandItem
onSelect={() => {
router.push(item.href);
setShowSearchCommand(false);
}}
>
<item.icon className="mr-2 h-4 w-4" />
<span>{item.label}</span>
</CommandItem>
</Link>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
</>
);
}
28 changes: 28 additions & 0 deletions app/(platform)/side-nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { UserButton } from "@clerk/nextjs";
import Link from "next/link";

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

import navItems from "./navItems";
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">
<SearchBar />
{navItems.map((item) => (
<Link key={item.href} href={item.href}>
<Button className="w-full justify-start" variant={"secondary"}>
<item.icon className="mr-3" />
{item.label}
</Button>
</Link>
))}
</div>
<div>
<UserButton afterSignOutUrl="/" />
</div>
</aside>
);
}
21 changes: 0 additions & 21 deletions app/dashboard/layout.tsx

This file was deleted.

Loading

0 comments on commit f42deba

Please sign in to comment.