feat: add note create, get and delete

This commit is contained in:
Kirill Siukhin 2025-07-15 01:13:43 +05:00
parent 8c38819d69
commit 1ba1949f41
6 changed files with 71 additions and 19 deletions

31
src/app/actions/notes.ts Normal file
View File

@ -0,0 +1,31 @@
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { desc, eq } from "drizzle-orm";
import { db } from "@/lib/db";
import { INote, IUser, notesTable, usersTable } from "@/lib/db/schema";
export async function createNote(user: IUser) {
const result = await db
.insert(notesTable)
.values({ authorId: user.id })
.returning({ id: usersTable.id });
const id = result[0].id;
redirect(`/notes/${id}`);
}
export async function getNotes(user: IUser): Promise<INote[]> {
return db
.select()
.from(notesTable)
.where(eq(notesTable.authorId, user.id))
.orderBy(desc(notesTable.lastEdited));
}
export async function deleteNote(note: INote) {
await db
.delete(notesTable)
.where(eq(notesTable.id, note.id));
revalidatePath("/notes");
}

View File

@ -2,11 +2,11 @@ import { Metadata } from "next";
import AuthForm from "@/components/forms/AuthForm";
export const metadata: Metadata = {
title: "Authenticate - Rhyme",
title: "Authentication - Rhyme",
description: "Register or log into Rhyme account to save, show and load notes",
};
export default function About() {
export default function Auth() {
return (
<>
<h1 className="font-bold text-xl mb-2">Authenticate</h1>

View File

@ -1,5 +1,6 @@
import { Metadata } from "next";
import { Noto_Sans_Mono } from "next/font/google";
import { getAuth } from "./actions/auth";
import Header from "@/components/Header";
import "./globals.css";
@ -10,18 +11,20 @@ const notoSansMono = Noto_Sans_Mono({
export const metadata: Metadata = {
title: "Rhyme",
description: "Line notes writing and sharing",
description: "Free service for writing and saving notes, lyrics, poetry and more",
};
export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const user = await getAuth();
return (
<html lang="en">
<body className={`${notoSansMono.variable} font-mono antialiased`}>
<Header />
<Header user={user} />
<div className="m-4">
{children}
</div>

View File

@ -1,4 +1,8 @@
import { getAuth } from "@/app/actions/auth";
import { getNotes } from "@/app/actions/notes";
import NoteCard from "@/components/ui/NoteCard";
import { Metadata } from "next";
import { redirect } from "next/navigation";
export const metadata: Metadata = {
title: "Notes - Rhyme",
@ -6,10 +10,16 @@ export const metadata: Metadata = {
};
export default async function Notes() {
const user = await getAuth();
if (!user) {
redirect("/auth");
}
const notes = await getNotes(user);
return (
<>
<h1 className="font-bold text-xl mb-4">Notes of (username):</h1>
{/* notes */}
<h1 className="font-bold text-xl mb-4">Notes of {user.username}:</h1>
{notes.map((note) => <NoteCard key={note.id} note={note} /> )}
</>
);
}

View File

@ -1,20 +1,22 @@
"use client";
import Link from "next/link";
import { CircleQuestionMark, List, Plus, UserRound, UserRoundMinus } from "lucide-react";
import { getAuth } from "@/app/actions/auth";
import { logOut } from "@/app/actions/auth";
import { createNote } from "@/app/actions/notes";
import { IUser } from "@/lib/db/schema";
import HeaderButton from "./ui/HeaderButton";
export default async function Header() {
const auth = await getAuth();
export default function Header({ user }: { user: IUser | null }) {
return (
<header className="flex items-center gap-6 m-4">
<Link href={auth ? "/notes" : "/"}>
<Link href={user ? "/notes" : "/"}>
<HeaderButton title="rhyme" className="bg-sky-600 hover:bg-sky-500" />
</Link>
{auth && (
{user && (
<div className="flex gap-2">
<HeaderButton title="new" icon={<Plus size={20} />} />
<HeaderButton title="new" icon={<Plus size={20} />} onClick={() => createNote(user)} />
<Link href="/notes">
<HeaderButton title="list" icon={<List size={20} />} />
</Link>
@ -25,8 +27,8 @@ export default async function Header() {
<Link href="/about">
<HeaderButton title="about" icon={<CircleQuestionMark size={20} />} />
</Link>
{auth ? (
<HeaderButton title="log out" icon={<UserRoundMinus size={20} />} />
{user ? (
<HeaderButton onClick={logOut} title="log out" icon={<UserRoundMinus size={20} />} />
) : (
<Link href="/auth">
<HeaderButton title="login" icon={<UserRound size={20} />} />

View File

@ -1,18 +1,24 @@
import Link from "next/link";
import { X } from "lucide-react";
import { INote } from "@/lib/db/schema";
import { deleteNote } from "@/app/actions/notes";
import IconOnlyButton from "./IconOnlyButton";
function makeTimestamp(date: Date) {
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
const hours = date.getHours();
const minutes = date.getMinutes();
const hours = date.getHours().toString().length === 1 ? `0${date.getHours()}` : date.getHours();
const minutes = date.getMinutes().toString().length === 1 ? `0${date.getMinutes()}` : date.getMinutes();
return `${day}.${month}.${year} ${hours}:${minutes}`;
}
export default function NoteCard({ note }: { note: INote }) {
const deleteNoteAction = async () => {
"use server";
await deleteNote(note);
};
return (
<div className="flex items-center mb-3 gap-4">
<Link href={`/notes/${note.id}`} className="flex flex-col border border-neutral-500 py-3 px-4 rounded-lg hover:bg-neutral-800 w-full">
@ -20,7 +26,7 @@ export default function NoteCard({ note }: { note: INote }) {
<i className="text-neutral-400 text-sm">Last time edited: {makeTimestamp(note.lastEdited)}</i>
<i className="text-neutral-400 text-sm">Creation date: {makeTimestamp(note.creationTime)}</i>
</Link>
<IconOnlyButton icon={<X size={24} />} />
<IconOnlyButton icon={<X size={24} />} onClick={deleteNoteAction} />
</div>
);
}