Skip to content

Commit

Permalink
Add note update and delete functionality.
Browse files Browse the repository at this point in the history
dark mode shad cn ui has been added
  • Loading branch information
abhishekHegde2000 committed Nov 24, 2023
1 parent a5fc32e commit cc2e728
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 29 deletions.
3 changes: 3 additions & 0 deletions src/app/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use client";

export { ThemeProvider } from "next-themes";
80 changes: 79 additions & 1 deletion src/app/api/notes/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import prisma from "@/lib/db/prisma";
import { createNoteSchema } from "@/lib/validation/note";
import {
createNoteSchema,
deleteNoteSchema,
updateNoteSchema,
} from "@/lib/validation/note";
import { auth } from "@clerk/nextjs";

export async function POST(req: Request) {
Expand Down Expand Up @@ -35,3 +39,77 @@ export async function POST(req: Request) {
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}

export async function PUT(req: Request) {
try {
const body = await req.json();

const parseResult = updateNoteSchema.safeParse(body);

if (!parseResult.success) {
console.error(parseResult.error);
return Response.json({ error: "Invalid input" }, { status: 400 });
}

const { id, title, content } = parseResult.data;

const note = await prisma.note.findUnique({ where: { id } });

if (!note) {
return Response.json({ error: "Note not found" }, { status: 404 });
}

const { userId } = auth();

if (!userId || userId !== note.userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}

const updatedNote = await prisma.note.update({
where: { id },
data: {
title,
content,
},
});

return Response.json({ updatedNote }, { status: 200 });
} catch (error) {
console.error(error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}

export async function DELETE(req: Request) {
try {
const body = await req.json();

const parseResult = deleteNoteSchema.safeParse(body);

if (!parseResult.success) {
console.error(parseResult.error);
return Response.json({ error: "Invalid input" }, { status: 400 });
}

const { id } = parseResult.data;

const note = await prisma.note.findUnique({ where: { id } });

if (!note) {
return Response.json({ error: "Note not found" }, { status: 404 });
}

const { userId } = auth();

if (!userId || userId !== note.userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}

await prisma.note.delete({ where: { id } });

return Response.json({ message: "Note deleted" }, { status: 200 });
} catch (error) {
console.error(error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}
5 changes: 4 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ClerkProvider } from "@clerk/nextjs";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ThemeProvider } from "./ThemeProvider";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });
Expand All @@ -18,7 +19,9 @@ export default function RootLayout({
return (
<ClerkProvider>
<html lang="en">
<body className={inter.className}>{children}</body>
<body className={inter.className}>
<ThemeProvider attribute="class">{children}</ThemeProvider>
</body>
</html>
</ClerkProvider>
);
Expand Down
18 changes: 14 additions & 4 deletions src/app/notes/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
"use client";

import logo from "@/assets/logo.png";
import AddNoteDialog from "@/components/AddNoteDialog";
import AddEditNoteDialog from "@/components/AddEditNoteDialog";
import ThemeToggleButton from "@/components/ThemeToggleButton";
import { Button } from "@/components/ui/button";
import { UserButton } from "@clerk/nextjs";
import { dark } from "@clerk/themes";
import { Plus } from "lucide-react";
import { useTheme } from "next-themes";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";

export default function NavBar() {
const [showAddNoteDialog, setShowAddNoteDialog] = useState(false);
const { theme } = useTheme();

const [showAddEditNoteDialog, setShowAddEditNoteDialog] = useState(false);

return (
<>
Expand All @@ -24,17 +29,22 @@ export default function NavBar() {
<UserButton
afterSignOutUrl="/"
appearance={{
baseTheme: theme === "dark" ? dark : undefined,
elements: { avatarBox: { width: "2.5rem", height: "2.5rem" } },
}}
/>
<Button onClick={() => setShowAddNoteDialog(true)}>
<ThemeToggleButton />
<Button onClick={() => setShowAddEditNoteDialog(true)}>
<Plus size={20} className="mr-2" />
Add Note
</Button>
</div>
</div>
</div>
<AddNoteDialog open={showAddNoteDialog} setOpen={setShowAddNoteDialog} />
<AddEditNoteDialog
open={showAddEditNoteDialog}
setOpen={setShowAddEditNoteDialog}
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CreateNoteSchema, createNoteSchema } from "@/lib/validation/note";
import { zodResolver } from "@hookform/resolvers/zod";
import { Note } from "@prisma/client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import {
Dialog,
Expand All @@ -21,43 +23,82 @@ import { Input } from "./ui/input";
import LoadingButton from "./ui/loading-button";
import { Textarea } from "./ui/textarea";

interface AddNoteDialogProps {
interface AddEditNoteDialogProps {
open: boolean;
setOpen: (open: boolean) => void;
noteToEdit?: Note;
}

export default function AddNoteDialog({ open, setOpen }: AddNoteDialogProps) {
export default function AddEditNoteDialog({
open,
setOpen,
noteToEdit,
}: AddEditNoteDialogProps) {
const [deleteInProgress, setDeleteInProgress] = useState(false);

const router = useRouter();

const form = useForm<CreateNoteSchema>({
resolver: zodResolver(createNoteSchema),
defaultValues: {
title: "",
content: "",
title: noteToEdit?.title || "",
content: noteToEdit?.content || "",
},
});

async function onSubmit(input: CreateNoteSchema) {
try {
if (noteToEdit) {
const response = await fetch("/api/notes", {
method: "PUT",
body: JSON.stringify({
id: noteToEdit.id,
...input,
}),
});
if (!response.ok) throw Error("Status code: " + response.status);
} else {
const response = await fetch("/api/notes", {
method: "POST",
body: JSON.stringify(input),
});
if (!response.ok) throw Error("Status code: " + response.status);
form.reset();
}
router.refresh();
setOpen(false);
} catch (error) {
console.error(error);
alert("Something went wrong. Please try again.");
}
}

async function deleteNote() {
if (!noteToEdit) return;
setDeleteInProgress(true);
try {
const response = await fetch("/api/notes", {
method: "POST",
body: JSON.stringify(input),
method: "DELETE",
body: JSON.stringify({
id: noteToEdit.id,
}),
});
if (!response.ok) throw Error("Status code: " + response.status);
form.reset();
router.refresh();
setOpen(false);
} catch (error) {
console.error(error);
alert("Something went wrong. Please try again.");
} finally {
setDeleteInProgress(false);
}
}

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Note</DialogTitle>
<DialogTitle>{noteToEdit ? "Edit Note" : "Add Note"}</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
Expand Down Expand Up @@ -87,10 +128,22 @@ export default function AddNoteDialog({ open, setOpen }: AddNoteDialogProps) {
</FormItem>
)}
/>
<DialogFooter>
<DialogFooter className="gap-1 sm:gap-0">
{noteToEdit && (
<LoadingButton
variant="destructive"
loading={deleteInProgress}
disabled={form.formState.isSubmitting}
onClick={deleteNote}
type="button"
>
Delete note
</LoadingButton>
)}
<LoadingButton
type="submit"
loading={form.formState.isSubmitting}
disabled={deleteInProgress}
>
Submit
</LoadingButton>
Expand All @@ -101,5 +154,3 @@ export default function AddNoteDialog({ open, setOpen }: AddNoteDialogProps) {
</Dialog>
);
}


40 changes: 28 additions & 12 deletions src/components/Note.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"use client";

import { Note as NoteModel } from "@prisma/client";
import { useState } from "react";
import AddEditNoteDialog from "./AddEditNoteDialog";
import {
Card,
CardContent,
Expand All @@ -12,24 +16,36 @@ interface NoteProps {
}

export default function Note({ note }: NoteProps) {
const [showEditDialog, setShowEditDialog] = useState(false);

const wasUpdated = note.updatedAt > note.createdAt;

const createdUpdatedAtTimestamp = (
wasUpdated ? note.updatedAt : note.createdAt
).toDateString();

return (
<Card>
<CardHeader>
<CardTitle>{note.title}</CardTitle>
<CardDescription>
{createdUpdatedAtTimestamp}
{wasUpdated && " (updated)"}
</CardDescription>
</CardHeader>
<CardContent>
<p className="whitespace-pre-line">{note.content}</p>
</CardContent>
</Card>
<>
<Card
className="cursor-pointer transition-shadow hover:shadow-lg"
onClick={() => setShowEditDialog(true)}
>
<CardHeader>
<CardTitle>{note.title}</CardTitle>
<CardDescription>
{createdUpdatedAtTimestamp}
{wasUpdated && " (updated)"}
</CardDescription>
</CardHeader>
<CardContent>
<p className="whitespace-pre-line">{note.content}</p>
</CardContent>
</Card>
<AddEditNoteDialog
open={showEditDialog}
setOpen={setShowEditDialog}
noteToEdit={note}
/>
</>
);
}
26 changes: 26 additions & 0 deletions src/components/ThemeToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "./ui/button";

export default function ThemeToggleButton() {
const { theme, setTheme } = useTheme();

return (
<Button
variant="outline"
size="icon"
className="rounded-full"
onClick={() => {
if (theme === "dark") {
setTheme("light");
} else {
setTheme("dark");
}
}}
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
);
}
8 changes: 8 additions & 0 deletions src/lib/validation/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ export const createNoteSchema = z.object({
});

export type CreateNoteSchema = z.infer<typeof createNoteSchema>;

export const updateNoteSchema = createNoteSchema.extend({
id: z.string().min(1),
});

export const deleteNoteSchema = z.object({
id: z.string().min(1),
});

0 comments on commit cc2e728

Please sign in to comment.