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+ }
0 commit comments