Skip to content

Commit dfe5906

Browse files
committed
Notes + Bug Fixes
1 parent fff30fb commit dfe5906

File tree

10 files changed

+3569
-1096
lines changed

10 files changed

+3569
-1096
lines changed

package-lock.json

Lines changed: 2015 additions & 1004 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prisma/schema.prisma

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ model PinItem {
7272
model NoteItem {
7373
id String @id @default(uuid())
7474
type VaultItemType @default(NOTE)
75-
note String
75+
title String
76+
content String
77+
titleIV String
78+
contentIV String
7679
createdAt DateTime @default(now())
7780
updatedAt DateTime @updatedAt
7881
userId String

src/app/(main)/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { VaultPage } from "@/components/vault/vault-page";
22
import { auth, currentUser } from "@clerk/nextjs/server";
3-
import { getPasswords, instantiateVault } from "../actions";
3+
import { getItems, instantiateVault } from "../actions";
44

55
export default async function Page() {
66
const { userId, redirectToSignIn } = await auth();
77

88
if (!userId) return redirectToSignIn();
99

10-
const user = await getPasswords();
10+
const user = await getItems();
1111

1212
if (!user) {
1313
const clerkUser = await currentUser();

src/app/actions.ts

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,119 @@ export async function createPasswordItem(
124124
return newPasswordItem;
125125
}
126126

127+
export async function updateNoteItem(
128+
id: string,
129+
newTitle: string,
130+
newContent: string,
131+
titleIV: string,
132+
contentIV: string
133+
) {
134+
const { userId } = await auth()
135+
136+
if (!userId) {
137+
throw new Error("Not authenticated");
138+
}
139+
140+
const user = await prismadb.user.findUnique({
141+
where: {
142+
id: userId,
143+
},
144+
include: {
145+
noteItems: true,
146+
},
147+
});
148+
149+
if (!user) {
150+
throw new Error("User not found");
151+
}
152+
153+
const noteItem = user.noteItems.find((item) => item.id === id);
154+
155+
if (!noteItem) {
156+
throw new Error("Note item not found");
157+
}
158+
159+
const item = await prismadb.noteItem.update({
160+
where: {
161+
id: noteItem.id,
162+
},
163+
data: {
164+
title: newTitle,
165+
content: newContent,
166+
titleIV,
167+
contentIV,
168+
},
169+
});
170+
171+
return item;
172+
}
173+
174+
export async function deleteNoteItem(id: string) {
175+
const { userId } = await auth()
176+
177+
if (!userId) {
178+
throw new Error("Not authenticated");
179+
}
180+
181+
const user = await prismadb.user.findUnique({
182+
where: {
183+
id: userId,
184+
},
185+
include: {
186+
noteItems: true,
187+
},
188+
});
189+
190+
if (!user) {
191+
throw new Error("User not found");
192+
}
193+
194+
const noteItem = user.noteItems.find((item) => item.id === id);
195+
196+
if (!noteItem) {
197+
throw new Error("Note item not found");
198+
}
199+
200+
await prismadb.noteItem.delete({
201+
where: {
202+
id: noteItem.id,
203+
},
204+
});
205+
206+
return { success: true };
207+
}
208+
209+
export async function createNoteItem(
210+
title: string,
211+
content: string,
212+
titleIV: string,
213+
contentIV: string
214+
) {
215+
const { userId } = await auth()
216+
217+
if (!userId) {
218+
throw new Error("Not authenticated");
219+
}
220+
221+
const newNoteItem = await prismadb.noteItem.create({
222+
data: {
223+
title,
224+
content,
225+
titleIV,
226+
contentIV,
227+
updatedAt: new Date().toISOString(),
228+
createdAt: new Date().toISOString(),
229+
user: {
230+
connect: {
231+
id: userId
232+
}
233+
}
234+
},
235+
});
236+
237+
return newNoteItem;
238+
}
239+
127240
export async function resetVault() {
128241
const { userId } = await auth()
129242

@@ -186,7 +299,7 @@ export async function instantiateVault(userId: string, username: string) {
186299
return vault;
187300
}
188301

189-
export async function getPasswords() {
302+
export async function getItems() {
190303
const { userId } = await auth()
191304

192305
if (!userId) {
@@ -215,3 +328,4 @@ export async function getPasswords() {
215328
},
216329
});
217330
}
331+

src/components/ui/context-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ function ContextMenuItem({
126126
data-inset={inset}
127127
data-variant={variant}
128128
className={cn(
129-
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
129+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground hover:cursor-pointer data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
130130
className
131131
)}
132132
{...props}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { createNoteItem } from "@/app/actions";
2+
import { Button } from "@/components/ui/button";
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
} from "@/components/ui/dialog";
11+
import { Input } from "@/components/ui/input";
12+
import { Textarea } from "@/components/ui/textarea";
13+
import { useState } from "react";
14+
import { toast } from "react-hot-toast";
15+
import { useUser } from "@clerk/nextjs";
16+
import {encrypt} from "@/utils/encryption";
17+
18+
interface CreateNoteDialogProps {
19+
open: boolean;
20+
onClose: () => void;
21+
}
22+
23+
export function CreateNoteDialog({ open, onClose }: CreateNoteDialogProps) {
24+
const { user } = useUser();
25+
const [isLoading, setIsLoading] = useState(false);
26+
const [title, setTitle] = useState("");
27+
const [content, setContent] = useState("");
28+
29+
const handleSubmit = async (e: React.FormEvent) => {
30+
e.preventDefault();
31+
if (!user) return;
32+
33+
setIsLoading(true);
34+
try {
35+
const { encryptedData: encryptedTitle, iv: titleIV } = await encrypt(
36+
title,
37+
user.id
38+
);
39+
const { encryptedData: encryptedContent, iv: contentIV } = await encrypt(
40+
content,
41+
user.id
42+
);
43+
44+
await createNoteItem(
45+
encryptedTitle,
46+
encryptedContent,
47+
titleIV,
48+
contentIV,
49+
);
50+
51+
toast.success("Note created successfully");
52+
onClose();
53+
setTitle("");
54+
setContent("");
55+
} catch (error) {
56+
console.error("Error creating note:", error);
57+
toast.error("Failed to create note");
58+
} finally {
59+
setIsLoading(false);
60+
}
61+
};
62+
63+
return (
64+
<Dialog open={open} onOpenChange={() => onClose()}>
65+
<DialogContent className="sm:max-w-[425px]">
66+
<form onSubmit={handleSubmit}>
67+
<DialogHeader>
68+
<DialogTitle>Create Note</DialogTitle>
69+
<DialogDescription>
70+
Add a new note to your vault
71+
</DialogDescription>
72+
</DialogHeader>
73+
<div className="grid gap-4 py-4">
74+
<Input
75+
placeholder="Title"
76+
value={title}
77+
onChange={(e) => setTitle(e.target.value)}
78+
required
79+
/>
80+
<Textarea
81+
placeholder="Content"
82+
value={content}
83+
onChange={(e) => setContent(e.target.value)}
84+
required
85+
rows={6}
86+
/>
87+
</div>
88+
<DialogFooter>
89+
<Button
90+
type="button"
91+
variant="outline"
92+
onClick={onClose}
93+
disabled={isLoading}
94+
>
95+
Cancel
96+
</Button>
97+
<Button type="submit" disabled={isLoading}>
98+
Create Note
99+
</Button>
100+
</DialogFooter>
101+
</form>
102+
</DialogContent>
103+
</Dialog>
104+
);
105+
}

0 commit comments

Comments
 (0)