Skip to content

Commit 99981ba

Browse files
authored
Merge pull request #75 from zecrypt-io/feature/shivil/notes
Feature/shivil/notes
2 parents 22c4216 + 3da2e26 commit 99981ba

File tree

12 files changed

+1705
-5
lines changed

12 files changed

+1705
-5
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { DashboardLayout } from "@/components/dashboard-layout";
2+
import { NotesContent } from "@/components/notes-content";
3+
4+
export default async function AccountsPage({ params }: { params: Promise<{ locale: string }> }) {
5+
const { locale } = await params; // Await params
6+
7+
return (
8+
<DashboardLayout locale={locale}>
9+
<NotesContent />
10+
</DashboardLayout>
11+
);
12+
}

packages/frontend-web/app/globals.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,26 @@ body {
190190
100% { transform: rotate(360deg); }
191191
}
192192

193+
}
194+
195+
/* TipTap/ProseMirror editor focus and caret styles */
196+
.ProseMirror {
197+
outline: none !important;
198+
border: 1.5px solid #e5e7eb; /* Subtle border (Tailwind border-gray-200) */
199+
border-radius: 0.5rem; /* Rounded corners */
200+
min-height: 200px;
201+
padding: 0.75rem;
202+
transition: border-color 0.2s, box-shadow 0.2s;
203+
font-size: 1rem;
204+
background: transparent;
205+
caret-color: #60a5fa; /* Lighter blue (Tailwind blue-400) */
206+
}
207+
208+
.ProseMirror:focus {
209+
border-color: #bae6fd; /* Very light blue (Tailwind sky-200) */
210+
box-shadow: 0 0 0 2px #bae6fd66; /* Very light blue glow */
211+
}
212+
213+
.ProseMirror:focus-visible {
214+
outline: none !important;
193215
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { useSelector } from "react-redux";
5+
import { RootState } from "@/libs/Redux/store";
6+
import { Button } from "@/components/ui/button";
7+
import { Input } from "@/components/ui/input";
8+
import { Textarea } from "@/components/ui/textarea";
9+
import { Plus, X, AlertCircle } from "lucide-react";
10+
import { Badge } from "@/components/ui/badge";
11+
import { useTranslator } from "@/hooks/use-translations";
12+
import { useNoteManagement } from "@/hooks/use-note-management";
13+
import { useEditor, EditorContent } from '@tiptap/react';
14+
import StarterKit from '@tiptap/starter-kit';
15+
16+
interface AddNoteDialogProps {
17+
open: boolean;
18+
onOpenChange: (open: boolean) => void;
19+
onClose: () => void;
20+
onNoteAdded: () => void;
21+
}
22+
23+
export function AddNoteDialog({ open, onOpenChange, onClose, onNoteAdded }: AddNoteDialogProps) {
24+
const { translate } = useTranslator();
25+
const [title, setTitle] = useState("");
26+
const [tags, setTags] = useState<string[]>([]);
27+
const [newTag, setNewTag] = useState("");
28+
const [error, setError] = useState("");
29+
const [isSubmitting, setIsSubmitting] = useState(false);
30+
31+
const selectedWorkspaceId = useSelector((state: RootState) => state.workspace.selectedWorkspaceId);
32+
const selectedProjectId = useSelector((state: RootState) => state.workspace.selectedProjectId);
33+
const { handleAddNote } = useNoteManagement({ selectedWorkspaceId, selectedProjectId });
34+
35+
// TipTap editor setup
36+
const editor = useEditor({
37+
extensions: [StarterKit],
38+
content: '',
39+
});
40+
41+
const addTag = (tag: string) => {
42+
if (tag && !tags.includes(tag)) {
43+
setTags([...tags, tag]);
44+
setNewTag("");
45+
}
46+
};
47+
48+
const removeTag = (tag: string) => {
49+
setTags(tags.filter((t) => t !== tag));
50+
};
51+
52+
const handleSubmit = async () => {
53+
if (!title || !editor || editor.isEmpty) {
54+
setError(translate("please_fill_all_required_fields", "notes"));
55+
return;
56+
}
57+
setIsSubmitting(true);
58+
setError("");
59+
try {
60+
const noteContent = JSON.stringify(editor.getJSON());
61+
await handleAddNote({ title, data: noteContent, tags });
62+
onNoteAdded();
63+
onOpenChange(false);
64+
} catch (e) {
65+
setError(translate("error_adding_note", "notes"));
66+
} finally {
67+
setIsSubmitting(false);
68+
}
69+
};
70+
71+
// Basic formatting toolbar
72+
const MenuBar = () => editor ? (
73+
<div className="flex gap-2 mb-2 border-b pb-2">
74+
<Button type="button" size="sm" aria-label="Bold" variant={editor.isActive('bold') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleBold().run()}><b>B</b></Button>
75+
<Button type="button" size="sm" aria-label="Italic" variant={editor.isActive('italic') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleItalic().run()}><i>I</i></Button>
76+
<Button type="button" size="sm" aria-label="Heading 1" variant={editor.isActive('heading', { level: 1 }) ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}>H1</Button>
77+
<Button type="button" size="sm" aria-label="Heading 2" variant={editor.isActive('heading', { level: 2 }) ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}>H2</Button>
78+
<Button type="button" size="sm" aria-label="Bullet List" variant={editor.isActive('bulletList') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleBulletList().run()}>• List</Button>
79+
<Button type="button" size="sm" aria-label="Ordered List" variant={editor.isActive('orderedList') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleOrderedList().run()}>1. List</Button>
80+
</div>
81+
) : null;
82+
83+
return (
84+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm overflow-y-auto py-6 transition-all duration-300">
85+
<div className="w-full max-w-lg rounded-2xl bg-card p-8 border border-border shadow-2xl relative my-auto animate-fade-in">
86+
<div className="mb-8 text-center">
87+
<h2 className="text-2xl font-bold tracking-tight">{translate("add_new_note", "notes", { default: "Add New Note" })}</h2>
88+
{error && (
89+
<div className="mt-4 p-2 bg-red-50 border border-red-200 rounded-md flex items-center gap-2 text-red-600">
90+
<AlertCircle className="h-4 w-4 flex-shrink-0" />
91+
<p className="text-sm">{error}</p>
92+
</div>
93+
)}
94+
</div>
95+
<div className="space-y-6 max-h-[70vh] overflow-y-auto pr-2">
96+
<div className="space-y-2">
97+
<label className="text-base font-semibold">
98+
{translate("note_title", "notes", { default: "Title" })} <span className="text-red-500">*</span>
99+
</label>
100+
<Input
101+
placeholder={translate("enter_note_title", "notes", { default: "Enter note title" })}
102+
value={title}
103+
onChange={(e) => setTitle(e.target.value)}
104+
required
105+
className="text-lg px-4 py-3 rounded-lg border-2 border-border focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all"
106+
/>
107+
</div>
108+
<div className="space-y-2">
109+
<label className="text-base font-semibold">
110+
{translate("note_content", "notes", { default: "Content" })} <span className="text-red-500">*</span>
111+
</label>
112+
<div className="sticky top-0 z-10 bg-card rounded-t-lg pb-2">
113+
<MenuBar />
114+
</div>
115+
<EditorContent editor={editor} className="min-h-[200px] border rounded-lg p-3 bg-background text-base focus:outline-none transition-all" />
116+
</div>
117+
<div className="space-y-2">
118+
<label className="text-base font-semibold">{translate("tags", "notes", { default: "Tags" })}</label>
119+
<div className="flex flex-wrap gap-2 mb-2">
120+
{tags.map((tag) => (
121+
<Badge key={tag} variant="secondary" className="flex items-center gap-1 px-3 py-1 rounded-full text-sm">
122+
{tag}
123+
<X className="h-3 w-3 cursor-pointer" onClick={() => removeTag(tag)} />
124+
</Badge>
125+
))}
126+
</div>
127+
<div className="flex items-center gap-2">
128+
<Input
129+
placeholder={translate("add_a_tag", "notes", { default: "Add a tag" })}
130+
value={newTag}
131+
onChange={(e) => setNewTag(e.target.value)}
132+
onKeyDown={(e) => {
133+
if (e.key === "Enter") {
134+
e.preventDefault();
135+
addTag(newTag);
136+
}
137+
}}
138+
className="rounded-full px-4 py-2"
139+
/>
140+
<Button type="button" variant="outline" size="icon" onClick={() => addTag(newTag)} className="rounded-full">
141+
<Plus className="h-4 w-4" />
142+
</Button>
143+
</div>
144+
</div>
145+
<div className="flex items-center justify-between gap-4 pt-6">
146+
<Button variant="outline" className="w-1/2 py-3 rounded-lg text-base" onClick={() => onOpenChange(false)} disabled={isSubmitting}>
147+
{translate("cancel", "notes", { default: "Cancel" })}
148+
</Button>
149+
<Button
150+
variant="default"
151+
className="w-1/2 py-3 rounded-lg text-base bg-primary text-primary-foreground shadow-md hover:bg-primary/90 transition-all"
152+
onClick={handleSubmit}
153+
disabled={isSubmitting}
154+
>
155+
{isSubmitting ? `${translate("adding", "notes", { default: "Adding..." })}` : translate("add_note", "notes", { default: "Add Note" })}
156+
</Button>
157+
</div>
158+
</div>
159+
</div>
160+
</div>
161+
);
162+
}

packages/frontend-web/components/dashboard-layout.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,40 @@ const navigationCategories: NavigationCategory[] = [
208208
}
209209
];
210210

211+
// Replace with your actual extension ID from chrome://extensions
212+
const EXTENSION_ID = "bbfbjmmofbhcnegicongikfhceadhdek";
213+
214+
function ExtensionMessenger() {
215+
const accessToken = useSelector((state: RootState) => state.user.userData?.access_token);
216+
const selectedWorkspaceId = useSelector((state: RootState) => state.workspace.selectedWorkspaceId);
217+
const selectedProjectId = useSelector((state: RootState) => state.workspace.selectedProjectId);
218+
219+
useEffect(() => {
220+
if (accessToken && selectedWorkspaceId && selectedProjectId) {
221+
if ((window as any).chrome && (window as any).chrome.runtime && (window as any).chrome.runtime.sendMessage) {
222+
(window as any).chrome.runtime.sendMessage(
223+
EXTENSION_ID,
224+
{
225+
type: "LOGIN",
226+
token: accessToken,
227+
workspaceId: selectedWorkspaceId,
228+
projectId: selectedProjectId,
229+
},
230+
(response: { success?: boolean }) => {
231+
if (response && response.success) {
232+
console.log("Zecrypt extension: Login data sent successfully!");
233+
} else {
234+
console.warn("Zecrypt extension: Failed to send login data.");
235+
}
236+
}
237+
);
238+
}
239+
}
240+
}, [accessToken, selectedWorkspaceId, selectedProjectId]);
241+
242+
return null;
243+
}
244+
211245
export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProps) {
212246
const pathname = usePathname();
213247
const router = useRouter();
@@ -564,6 +598,7 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
564598

565599
return (
566600
<div className="flex min-h-screen bg-background">
601+
<ExtensionMessenger />
567602
<ChatWidget />
568603
<SearchModal
569604
isOpen={showSearchModal}

0 commit comments

Comments
 (0)