1+ import { Dialog , Transition } from "@headlessui/react" ;
2+ import { Fragment , useState } from "react" ;
3+ import { XMarkIcon } from "@heroicons/react/24/outline" ;
4+ import { BuildRequest } from "@/lib/api/types" ;
5+
6+ interface BountyModalProps {
7+ buildRequest : BuildRequest ;
8+ isOpen : boolean ;
9+ onClose : ( ) => void ;
10+ }
11+
12+ export function BountyModal ( { buildRequest, isOpen, onClose } : BountyModalProps ) {
13+ const [ amount , setAmount ] = useState ( "" ) ;
14+ const [ currency , setCurrency ] = useState ( "USDC" ) ;
15+ const [ deadline , setDeadline ] = useState ( "" ) ;
16+ const [ description , setDescription ] = useState ( "" ) ;
17+
18+ // Calculate default deadline (2 weeks from now)
19+ const twoWeeksFromNow = new Date ( ) ;
20+ twoWeeksFromNow . setDate ( twoWeeksFromNow . getDate ( ) + 14 ) ;
21+ const defaultDeadline = twoWeeksFromNow . toISOString ( ) . split ( 'T' ) [ 0 ] ;
22+
23+ const handleSubmit = ( e : React . FormEvent ) => {
24+ e . preventDefault ( ) ;
25+
26+ // Create the Warpcast intent URL
27+ const intentUrl = new URL ( "https://warpcast.com/~/compose" ) ;
28+
29+ // Format the text for the bounty
30+ const bountyText = `I'd love for someone to build this!\n\nAmount: ${ amount } ${ currency } ${ deadline ? `\nDeadline: ${ deadline } ` : '' } ${ description ? `\n\n${ description } ` : '' } \n\n@bountybot` ;
31+
32+ intentUrl . searchParams . set ( "text" , bountyText ) ;
33+ intentUrl . searchParams . set (
34+ "embeds[]" ,
35+ `https://warpcast.com/${ buildRequest . author . username } /${ buildRequest . hash } ` ,
36+ ) ;
37+
38+ // Open in new tab
39+ window . open ( intentUrl . toString ( ) , "_blank" ) ;
40+ onClose ( ) ;
41+ } ;
42+
43+ return (
44+ < Transition appear show = { isOpen } as = { Fragment } >
45+ < Dialog as = "div" className = "relative z-50" onClose = { onClose } >
46+ < Transition . Child
47+ as = { Fragment }
48+ enter = "ease-out duration-300"
49+ enterFrom = "opacity-0"
50+ enterTo = "opacity-100"
51+ leave = "ease-in duration-200"
52+ leaveFrom = "opacity-100"
53+ leaveTo = "opacity-0"
54+ >
55+ < div className = "fixed inset-0 bg-black/80 backdrop-blur-sm" />
56+ </ Transition . Child >
57+
58+ < div className = "fixed inset-0 overflow-y-auto" >
59+ < div className = "flex min-h-full items-center justify-center p-4 text-center" >
60+ < Transition . Child
61+ as = { Fragment }
62+ enter = "ease-out duration-300"
63+ enterFrom = "opacity-0 scale-95"
64+ enterTo = "opacity-100 scale-100"
65+ leave = "ease-in duration-200"
66+ leaveFrom = "opacity-100 scale-100"
67+ leaveTo = "opacity-0 scale-95"
68+ >
69+ < Dialog . Panel className = "w-full max-w-2xl transform overflow-hidden rounded-2xl bg-purple-900/90 p-6 text-left align-middle shadow-xl transition-all border border-purple-400/20" >
70+ < div className = "flex items-center justify-between mb-6" >
71+ < Dialog . Title
72+ as = "h3"
73+ className = "text-xl font-medium leading-6 text-purple-100"
74+ >
75+ Post Bounty
76+ </ Dialog . Title >
77+ < button
78+ onClick = { onClose }
79+ className = "text-purple-300 hover:text-white transition-colors"
80+ >
81+ < XMarkIcon className = "w-6 h-6" />
82+ </ button >
83+ </ div >
84+
85+ < div className = "mb-6" >
86+ < p className = "text-sm text-purple-200" >
87+ Create a bounty for this build request using @bountybot
88+ </ p >
89+ </ div >
90+
91+ < form onSubmit = { handleSubmit } className = "space-y-6" >
92+ < div className = "flex space-x-4" >
93+ < div className = "flex-1" >
94+ < label
95+ htmlFor = "amount"
96+ className = "block text-sm font-medium text-purple-200 mb-2"
97+ >
98+ Amount
99+ </ label >
100+ < input
101+ type = "number"
102+ id = "amount"
103+ value = { amount }
104+ onChange = { ( e ) => setAmount ( e . target . value ) }
105+ placeholder = "50"
106+ className = "w-full px-4 py-2 bg-purple-800/50 border border-purple-700 rounded-lg
107+ text-purple-100 placeholder-purple-400 focus:outline-none focus:ring-2
108+ focus:ring-purple-500 focus:border-transparent"
109+ required
110+ />
111+ </ div >
112+ < div className = "w-1/3" >
113+ < label
114+ htmlFor = "currency"
115+ className = "block text-sm font-medium text-purple-200 mb-2"
116+ >
117+ Currency
118+ </ label >
119+ < select
120+ id = "currency"
121+ value = { currency }
122+ onChange = { ( e ) => setCurrency ( e . target . value ) }
123+ className = "w-full px-4 py-2 bg-purple-800/50 border border-purple-700 rounded-lg
124+ text-purple-100 focus:outline-none focus:ring-2 focus:ring-purple-500
125+ focus:border-transparent"
126+ >
127+ < option value = "USDC" > USDC</ option >
128+ < option value = "ETH" > ETH</ option >
129+ < option value = "DEGEN" > DEGEN</ option >
130+ </ select >
131+ </ div >
132+ </ div >
133+
134+ < div >
135+ < label
136+ htmlFor = "deadline"
137+ className = "block text-sm font-medium text-purple-200 mb-2"
138+ >
139+ Deadline (Optional)
140+ </ label >
141+ < input
142+ type = "date"
143+ id = "deadline"
144+ value = { deadline }
145+ onChange = { ( e ) => setDeadline ( e . target . value ) }
146+ min = { new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] }
147+ placeholder = { defaultDeadline }
148+ className = "w-full px-4 py-2 bg-purple-800/50 border border-purple-700 rounded-lg
149+ text-purple-100 placeholder-purple-400 focus:outline-none focus:ring-2
150+ focus:ring-purple-500 focus:border-transparent"
151+ />
152+ </ div >
153+
154+ < div >
155+ < label
156+ htmlFor = "description"
157+ className = "block text-sm font-medium text-purple-200 mb-2"
158+ >
159+ Additional Description (Optional)
160+ </ label >
161+ < textarea
162+ id = "description"
163+ rows = { 3 }
164+ value = { description }
165+ onChange = { ( e ) => setDescription ( e . target . value ) }
166+ placeholder = "Add any additional details or requirements..."
167+ className = "w-full px-4 py-2 bg-purple-800/50 border border-purple-700 rounded-lg
168+ text-purple-100 placeholder-purple-400 focus:outline-none focus:ring-2
169+ focus:ring-purple-500 focus:border-transparent"
170+ />
171+ </ div >
172+
173+ < div className = "flex justify-end space-x-4" >
174+ < button
175+ type = "button"
176+ onClick = { onClose }
177+ className = "px-4 py-2 text-purple-200 hover:text-white transition-colors"
178+ >
179+ Cancel
180+ </ button >
181+ < button
182+ type = "submit"
183+ className = "group relative inline-flex items-center justify-center overflow-hidden rounded-lg
184+ bg-gradient-to-r from-yellow-400 to-yellow-300 p-[2px] font-medium text-purple-900
185+ shadow-xl shadow-yellow-400/20 transition-all duration-300 hover:shadow-yellow-400/40
186+ hover:scale-[1.02] active:scale-[0.98]"
187+ >
188+ < span
189+ className = "relative flex items-center space-x-2 rounded-lg bg-gradient-to-r from-yellow-400
190+ to-yellow-300 px-6 py-2.5 transition-all duration-200 ease-out group-hover:bg-opacity-0
191+ group-hover:from-yellow-300 group-hover:to-yellow-200"
192+ >
193+ Post Bounty
194+ </ span >
195+ </ button >
196+ </ div >
197+ </ form >
198+ </ Dialog . Panel >
199+ </ Transition . Child >
200+ </ div >
201+ </ div >
202+ </ Dialog >
203+ </ Transition >
204+ ) ;
205+ }
0 commit comments