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