1+ import  {  useState ,  useEffect  }  from  "react" ; 
2+ import  {  useMutation  }  from  "@tanstack/react-query" ; 
3+ import  {  useLocation  }  from  "wouter" ; 
4+ import  {  useToast  }  from  "@/hooks/use-toast" ; 
5+ import  {  apiRequest ,  queryClient  }  from  "@/lib/queryClient" ; 
6+ import  {  isUnauthorizedError  }  from  "@/lib/authUtils" ; 
7+ import  {  Dialog ,  DialogContent  }  from  "@/components/ui/dialog" ; 
8+ import  {  Button  }  from  "@/components/ui/button" ; 
9+ import  {  Input  }  from  "@/components/ui/input" ; 
10+ import  {  Label  }  from  "@/components/ui/label" ; 
11+ import  {  Textarea  }  from  "@/components/ui/textarea" ; 
12+ import  {  DropdownMenu ,  DropdownMenuContent ,  DropdownMenuItem ,  DropdownMenuTrigger  }  from  "@/components/ui/dropdown-menu" ; 
13+ import  {  Star ,  Plus ,  Loader2 ,  ChevronDown  }  from  "lucide-react" ; 
14+ import  type  {  WishlistItem  }  from  "@shared/schema" ; 
15+ 
16+ interface  AddWishlistModalProps  { 
17+   open : boolean ; 
18+   onOpenChange : ( open : boolean )  =>  void ; 
19+   editingItem ?: WishlistItem  |  null ; 
20+ } 
21+ 
22+ export  default  function  AddWishlistModal ( {  open,  onOpenChange,  editingItem } : AddWishlistModalProps )  { 
23+   const  {  toast }  =  useToast ( ) ; 
24+   const  [ ,  setLocation ]  =  useLocation ( ) ; 
25+   
26+   // Form state 
27+   const  [ title ,  setTitle ]  =  useState ( editingItem ?. title  ||  "" ) ; 
28+   const  [ description ,  setDescription ]  =  useState ( editingItem ?. description  ||  "" ) ; 
29+   const  [ priority ,  setPriority ]  =  useState ( editingItem ?. priority  ||  "medium" ) ; 
30+ 
31+   // Reset form when modal opens/closes or editing item changes 
32+   useEffect ( ( )  =>  { 
33+     if  ( open )  { 
34+       setTitle ( editingItem ?. title  ||  "" ) ; 
35+       setDescription ( editingItem ?. description  ||  "" ) ; 
36+       setPriority ( editingItem ?. priority  ||  "medium" ) ; 
37+     } 
38+   } ,  [ open ,  editingItem ] ) ; 
39+ 
40+   const  resetForm  =  ( )  =>  { 
41+     setTitle ( "" ) ; 
42+     setDescription ( "" ) ; 
43+     setPriority ( "medium" ) ; 
44+   } ; 
45+ 
46+   // Add/Update wishlist item mutation 
47+   const  wishlistMutation  =  useMutation ( { 
48+     mutationFn : async  ( )  =>  { 
49+       const  itemData  =  {  title,  description,  priority } ; 
50+       
51+       if  ( editingItem )  { 
52+         await  apiRequest ( "PUT" ,  `/api/wishlist/${ editingItem . id }  ` ,  itemData ) ; 
53+       }  else  { 
54+         await  apiRequest ( "POST" ,  "/api/wishlist" ,  itemData ) ; 
55+       } 
56+     } , 
57+     onSuccess : ( data )  =>  { 
58+       queryClient . invalidateQueries ( {  queryKey : [ "/api/profile" ]  } ) ; 
59+       toast ( {  
60+         title : "Success" ,  
61+         description : editingItem  ? "Wishlist item updated successfully!"  : "Wishlist item added successfully!"  
62+       } ) ; 
63+       resetForm ( ) ; 
64+       onOpenChange ( false ) ; 
65+       
66+       // Redirect to wishlist page with new item highlighted 
67+       if  ( ! editingItem )  { 
68+         setLocation ( "/wishlist?highlight=new" ) ; 
69+       } 
70+     } , 
71+     onError : ( error )  =>  { 
72+       if  ( isUnauthorizedError ( error ) )  { 
73+         toast ( { 
74+           title : "Unauthorized" , 
75+           description : "You are logged out. Logging in again..." , 
76+           variant : "destructive" , 
77+         } ) ; 
78+         setTimeout ( ( )  =>  { 
79+           window . location . href  =  "/api/login" ; 
80+         } ,  500 ) ; 
81+         return ; 
82+       } 
83+       toast ( { 
84+         title : "Error" , 
85+         description : `Failed to ${ editingItem  ? 'update'  : 'add' }   wishlist item. Please try again.` , 
86+         variant : "destructive" , 
87+       } ) ; 
88+     } , 
89+   } ) ; 
90+ 
91+   const  handleSubmit  =  ( )  =>  { 
92+     if  ( ! title . trim ( ) )  { 
93+       toast ( { 
94+         title : "Validation Error" , 
95+         description : "Please fill in the title field." , 
96+         variant : "destructive" , 
97+       } ) ; 
98+       return ; 
99+     } 
100+     wishlistMutation . mutate ( ) ; 
101+   } ; 
102+ 
103+   const  handleCancel  =  ( )  =>  { 
104+     resetForm ( ) ; 
105+     onOpenChange ( false ) ; 
106+   } ; 
107+ 
108+   return  ( 
109+     < Dialog  open = { open }  onOpenChange = { onOpenChange } > 
110+       < DialogContent  className = "max-w-2xl mx-auto bg-gradient-to-br from-white via-blue-50/30 to-purple-50/30 border-0 shadow-2xl" > 
111+         { /* Header */ } 
112+         < div  className = "text-center mb-8 pt-2" > 
113+           < div  className = "w-16 h-16 bg-gradient-to-br from-blue-100 via-indigo-100 to-purple-100 rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg" > 
114+             < Star  className = "w-8 h-8 text-blue-600"  /> 
115+           </ div > 
116+           < h2  className = "text-2xl font-bold bg-gradient-to-r from-blue-700 via-indigo-700 to-purple-700 bg-clip-text text-transparent" > 
117+             { editingItem  ? "Edit Wishlist Item"  : "Add Wishlist Item" } 
118+           </ h2 > 
119+           < p  className = "text-gray-600 mt-2" > 
120+             { editingItem  ? "Update your wishlist item details"  : "Share what you're looking to achieve or acquire" } 
121+           </ p > 
122+         </ div > 
123+ 
124+         { /* Form Content */ } 
125+         < div  className = "space-y-6" > 
126+           { /* Title and Priority Row */ } 
127+           < div  className = "grid grid-cols-1 md:grid-cols-2 gap-4" > 
128+             < div  className = "space-y-3" > 
129+               < div  className = "flex items-center space-x-2" > 
130+                 < div  className = "w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center" > 
131+                   < Star  className = "h-3.5 w-3.5 text-blue-600"  /> 
132+                 </ div > 
133+                 < Label  htmlFor = "title"  className = "text-sm font-medium text-gray-900" > Title</ Label > 
134+               </ div > 
135+               < div  className = "bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4" > 
136+                 < Input 
137+                   id = "title" 
138+                   value = { title } 
139+                   onChange = { ( e )  =>  setTitle ( e . target . value ) } 
140+                   placeholder = "Learn Spanish, Find a mentor..." 
141+                   className = "border-0 bg-white/70 backdrop-blur-sm shadow-sm focus:ring-2 focus:ring-blue-200 focus:bg-white transition-all duration-200" 
142+                 /> 
143+               </ div > 
144+             </ div > 
145+ 
146+             < div  className = "space-y-3" > 
147+               < div  className = "flex items-center space-x-2" > 
148+                 < div  className = "w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center" > 
149+                   < Star  className = "h-3.5 w-3.5 text-blue-600"  /> 
150+                 </ div > 
151+                 < Label  className = "text-sm font-medium text-gray-900" > Priority Level</ Label > 
152+               </ div > 
153+               < div  className = "bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4" > 
154+                 < DropdownMenu > 
155+                   < DropdownMenuTrigger  asChild > 
156+                     < Button  variant = "ghost"  className = "w-full justify-between border border-gray-200 hover:bg-gray-50" > 
157+                       { priority  ===  "urgent"  &&  "🔥 Urgent!" } 
158+                       { priority  ===  "high"  &&  "High Priority" } 
159+                       { priority  ===  "medium"  &&  "Medium Priority" } 
160+                       { priority  ===  "low"  &&  "Low Priority" } 
161+                       < ChevronDown  className = "h-4 w-4"  /> 
162+                     </ Button > 
163+                   </ DropdownMenuTrigger > 
164+                   < DropdownMenuContent  className = "w-48" > 
165+                     < DropdownMenuItem  onClick = { ( )  =>  setPriority ( "urgent" ) } > 
166+                       🔥 Urgent!
167+                     </ DropdownMenuItem > 
168+                     < DropdownMenuItem  onClick = { ( )  =>  setPriority ( "high" ) } > 
169+                       High Priority
170+                     </ DropdownMenuItem > 
171+                     < DropdownMenuItem  onClick = { ( )  =>  setPriority ( "medium" ) } > 
172+                       Medium Priority
173+                     </ DropdownMenuItem > 
174+                     < DropdownMenuItem  onClick = { ( )  =>  setPriority ( "low" ) } > 
175+                       Low Priority
176+                     </ DropdownMenuItem > 
177+                   </ DropdownMenuContent > 
178+                 </ DropdownMenu > 
179+               </ div > 
180+             </ div > 
181+           </ div > 
182+ 
183+           { /* Description */ } 
184+           < div  className = "space-y-3" > 
185+             < div  className = "flex items-center space-x-2" > 
186+               < div  className = "w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center" > 
187+                 < Star  className = "h-3.5 w-3.5 text-blue-600"  /> 
188+               </ div > 
189+               < Label  htmlFor = "description"  className = "text-sm font-medium text-gray-900" > Description</ Label > 
190+             </ div > 
191+             < div  className = "bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4" > 
192+               < Textarea 
193+                 id = "description" 
194+                 value = { description } 
195+                 onChange = { ( e )  =>  setDescription ( e . target . value ) } 
196+                 placeholder = "Describe what you're looking for and why it matters to you..." 
197+                 rows = { 4 } 
198+                 className = "border-0 bg-white/70 backdrop-blur-sm shadow-sm focus:ring-2 focus:ring-blue-200 focus:bg-white transition-all duration-200 resize-none" 
199+               /> 
200+             </ div > 
201+           </ div > 
202+         </ div > 
203+ 
204+         { /* Action Buttons */ } 
205+         < div  className = "flex justify-center sm:justify-end space-x-3 pt-3" > 
206+           < Button  
207+             variant = "outline"  
208+             onClick = { handleCancel } 
209+             className = "px-6 border-gray-200 text-gray-600 hover:bg-gray-50 rounded-lg" 
210+           > 
211+             Cancel
212+           </ Button > 
213+           < Button  
214+             onClick = { handleSubmit } 
215+             disabled = { wishlistMutation . isPending } 
216+             className = "px-8 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg shadow-lg hover:shadow-xl transition-all duration-200" 
217+           > 
218+             { wishlistMutation . isPending  ? ( 
219+               < > 
220+                 < Loader2  className = "mr-2 h-4 w-4 animate-spin"  /> 
221+                 { editingItem  ? "Updating..."  : "Creating..." } 
222+               </ > 
223+             )  : ( 
224+               < > 
225+                 < Star  className = "mr-2 h-4 w-4"  /> 
226+                 { editingItem  ? "Update Item"  : "Add to Wishlist" } 
227+               </ > 
228+             ) } 
229+           </ Button > 
230+         </ div > 
231+       </ DialogContent > 
232+     </ Dialog > 
233+   ) ; 
234+ } 
0 commit comments