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