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