feat: add notes list
This commit is contained in:
parent
b1979aa8bb
commit
523f016a7c
735
package-lock.json
generated
735
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<>
|
||||
<Header showToolbar />
|
||||
<Header allowExport />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
@ -13,12 +13,10 @@ export default async function Home() {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-8">
|
||||
<Editor />
|
||||
{!auth && (
|
||||
<i className="text-center text-sm text-neutral-400">
|
||||
Changes are not saved!<br />
|
||||
<Link href="/login" className="font-bold hover:underline">Log in</Link> to save your notes.
|
||||
</i>
|
||||
)}
|
||||
<i className="text-center text-sm text-neutral-400">
|
||||
Changes are not saved!<br />
|
||||
<Link href="/login" className="font-bold hover:underline">Log in</Link> to save your notes.
|
||||
</i>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export const metadata: Metadata = {
|
||||
export default function About() {
|
||||
return (
|
||||
<>
|
||||
<div className="m-4">
|
||||
<div>
|
||||
<h1 className="font-bold text-xl mb-2">Authenticate</h1>
|
||||
<div className="flex gap-4 flex-col md:flex-row">
|
||||
<AuthForm />
|
||||
|
17
src/app/(noneditor)/notes/new/page.tsx
Normal file
17
src/app/(noneditor)/notes/new/page.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { createNote } from "@/lib/notes";
|
||||
|
||||
export default async function NewNote() {
|
||||
const noteId = await createNote();
|
||||
|
||||
if (noteId) {
|
||||
redirect(`/notes/${noteId}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-center mt-4">
|
||||
Failed to create a new note!<br />
|
||||
Please, try again.
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,14 +1,27 @@
|
||||
import { Metadata } from "next";
|
||||
import { getNotes } from "@/lib/notes";
|
||||
import NoteCard from "@/components/NoteCard";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Notes - Rhyme",
|
||||
description: "View, create and edit your notes",
|
||||
description: "View and edit your notes",
|
||||
};
|
||||
|
||||
export default function Notes() {
|
||||
export default async function Notes() {
|
||||
const notes = await getNotes();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Notes</h1>
|
||||
</div>
|
||||
<>
|
||||
<h1 className="font-bold text-xl mb-4">Notes</h1>
|
||||
{notes ? (
|
||||
notes.length > 0 ? (
|
||||
notes.map((note) => <NoteCard key={note.id} note={note} />)
|
||||
) : (
|
||||
<span>You don't have any saved notes!</span>
|
||||
)
|
||||
) : (
|
||||
<span>Failed to get notes! Please, try again.</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { logOut } from "@/app/actions";
|
||||
import { getAuth } from "@/lib/auth";
|
||||
import HeaderButton from "./HeaderButton";
|
||||
|
||||
export default async function Header({ showToolbar = false }: { showToolbar?: boolean }) {
|
||||
export default async function Header({ allowExport = false }: { allowExport?: boolean }) {
|
||||
const auth = await getAuth();
|
||||
|
||||
return (
|
||||
@ -24,7 +24,7 @@ export default async function Header({ showToolbar = false }: { showToolbar?: bo
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
{showToolbar && <HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} />}
|
||||
{allowExport && <HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} />}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 ml-auto">
|
||||
|
25
src/components/NoteCard.tsx
Normal file
25
src/components/NoteCard.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import Link from "next/link";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { X } from "lucide-react";
|
||||
import { notesTable } from "@/lib/db/schema";
|
||||
import { deleteNote } from "@/lib/notes";
|
||||
import IconOnlyButton from "./ui/IconOnlyButton";
|
||||
|
||||
export default function NoteCard({ note }: { note: typeof notesTable.$inferSelect }) {
|
||||
const handleDeleteNote = async () => {
|
||||
"use server";
|
||||
await deleteNote(note.id);
|
||||
revalidatePath("/notes");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center mb-2 gap-4">
|
||||
<Link href={`/notes/${note.id}`} className="flex flex-col border-2 border-neutral-700 py-3 px-4 rounded-lg hover:bg-neutral-800 w-full">
|
||||
<h1 className="font-bold">{note.name}</h1>
|
||||
<i className="text-neutral-400 text-sm">Creation date: {note.creationTime.toString()}</i>
|
||||
<i className="text-neutral-400 text-sm">Last time edited: {note.lastEdited.toString()}</i>
|
||||
</Link>
|
||||
<IconOnlyButton icon={<X size={24} />} onClick={handleDeleteNote} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { usersTable } from "./db/schema";
|
||||
import { db } from "./db";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export async function getAuth() {
|
||||
@ -9,5 +12,17 @@ export async function getAuth() {
|
||||
}
|
||||
|
||||
const decodedToken = jwt.decode(token) as jwt.JwtPayload;
|
||||
return { username: decodedToken.sub };
|
||||
const username = decodedToken.sub;
|
||||
if (!username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const users = await db.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.username, username));
|
||||
if (users.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return users[0];
|
||||
}
|
||||
|
@ -1,26 +1,41 @@
|
||||
import { pgTable, integer, json, varchar } from "drizzle-orm/pg-core";
|
||||
import { pgTable, json, varchar, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
import { relations } from "drizzle-orm";
|
||||
|
||||
export const usersTable = pgTable("users", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
username: varchar({ length: 50 }).notNull().unique(),
|
||||
password: varchar().notNull(),
|
||||
});
|
||||
|
||||
export const usersRelations = relations(usersTable, ({ many }) => ({
|
||||
blocks: many(blocksTable),
|
||||
notes: many(notesTable),
|
||||
}));
|
||||
|
||||
export const blocksTable = pgTable("blocks", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
tag: varchar({ length: 100 }).notNull().default(""),
|
||||
lines: json().notNull().default([]),
|
||||
authorId: integer(),
|
||||
export const notesTable = pgTable("notes", {
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
name: varchar({ length: 50 }).notNull().default("Untitled"),
|
||||
creationTime: timestamp().notNull().defaultNow(),
|
||||
lastEdited: timestamp().notNull().defaultNow(),
|
||||
authorId: uuid(),
|
||||
});
|
||||
|
||||
export const blocksRelations = relations(blocksTable, ({ one }) => ({
|
||||
export const notesRelations = relations(notesTable, ({ one }) => ({
|
||||
author: one(usersTable, {
|
||||
fields: [blocksTable.authorId],
|
||||
fields: [notesTable.authorId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const blocksTable = pgTable("blocks", {
|
||||
id: uuid().primaryKey().defaultRandom(),
|
||||
tag: varchar({ length: 100 }).notNull().default(""),
|
||||
lines: json().notNull().default([]),
|
||||
noteId: uuid(),
|
||||
});
|
||||
|
||||
export const blocksRelations = relations(blocksTable, ({ one }) => ({
|
||||
note: one(notesTable, {
|
||||
fields: [blocksTable.noteId],
|
||||
references: [notesTable.id],
|
||||
}),
|
||||
}));
|
||||
|
38
src/lib/notes.ts
Normal file
38
src/lib/notes.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { notesTable } from "./db/schema";
|
||||
import { getAuth } from "./auth";
|
||||
import { db } from "./db";
|
||||
|
||||
export async function getNotes() {
|
||||
const auth = await getAuth();
|
||||
if (!auth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return db.select()
|
||||
.from(notesTable)
|
||||
.where(eq(notesTable.authorId, auth.id));
|
||||
}
|
||||
|
||||
export async function createNote() {
|
||||
const auth = await getAuth();
|
||||
if (!auth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const note = await db
|
||||
.insert(notesTable)
|
||||
.values({ authorId: auth.id })
|
||||
.returning({ id: notesTable.id });
|
||||
|
||||
return note[0].id;
|
||||
}
|
||||
|
||||
export async function deleteNote(noteId: string) {
|
||||
const auth = await getAuth();
|
||||
if (!auth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await db.delete(notesTable).where(eq(notesTable.id, noteId));
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getAuth } from "./lib/auth";
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const auth = await getAuth();
|
||||
|
||||
if (auth) {
|
||||
return NextResponse.next();
|
||||
} else {
|
||||
return NextResponse.redirect(new URL("/auth", request.url));
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: "/notes/:path*",
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user