Skip to content

Commit d1fa5a0

Browse files
committed
fix: improve mobile layout of BuildRequestCard by stacking buttons above date/engagement
1 parent 66ded78 commit d1fa5a0

File tree

3 files changed

+337
-49
lines changed

3 files changed

+337
-49
lines changed

components/BountyModal.tsx

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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+
}

components/BuildRequestCard.tsx

Lines changed: 85 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useAuth } from "@/lib/hooks/useAuth";
77
import { SafeImage } from "./SafeImage";
88
import { useRouter } from "next/navigation";
99
import { useSession } from "next-auth/react";
10+
import { BountyModal } from "./BountyModal";
1011

1112
interface BuildRequestCardProps {
1213
buildRequest: BuildRequest;
@@ -146,6 +147,7 @@ export function BuildRequestCard({ buildRequest }: BuildRequestCardProps) {
146147
const { data: session } = useSession();
147148
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
148149
const [isClaimModalOpen, setIsClaimModalOpen] = useState(false);
150+
const [isBountyModalOpen, setIsBountyModalOpen] = useState(false);
149151
const [authMessage, setAuthMessage] = useState("");
150152
const { isAuthenticated } = useAuth();
151153
const router = useRouter();
@@ -355,11 +357,82 @@ export function BuildRequestCard({ buildRequest }: BuildRequestCardProps) {
355357
</div>
356358

357359
<div
358-
className="flex items-center justify-between mt-4"
360+
className="flex flex-col space-y-4 md:space-y-0 md:flex-row md:items-center md:justify-between mt-4"
359361
data-oid="s3j_83f"
360362
>
363+
{/* Buttons - Full width on mobile, normal on desktop */}
364+
<div className="flex items-center space-x-2 w-full md:w-auto order-1 md:order-2">
365+
<button
366+
onClick={handleBuildClick}
367+
className="flex-1 md:flex-none group relative inline-flex items-center justify-center overflow-hidden rounded-lg
368+
bg-gradient-to-r from-emerald-400 to-emerald-300 p-[2px] font-medium text-emerald-900
369+
shadow-xl shadow-emerald-400/20 transition-all duration-300 hover:shadow-emerald-400/40
370+
hover:scale-[1.02] active:scale-[0.98]"
371+
data-oid="bxp4bi4"
372+
>
373+
<span
374+
className="relative flex items-center space-x-1.5 rounded-lg bg-gradient-to-r from-emerald-400
375+
to-emerald-300 px-2 py-1.5 text-sm transition-all duration-200 ease-out group-hover:bg-opacity-0
376+
group-hover:from-emerald-300 group-hover:to-emerald-200 w-full justify-center md:justify-start md:w-auto"
377+
data-oid="4ox26cb"
378+
>
379+
<svg
380+
className="w-3.5 h-3.5 transform transition-transform duration-200 group-hover:translate-x-1"
381+
fill="none"
382+
stroke="currentColor"
383+
viewBox="0 0 24 24"
384+
data-oid="_l3tg5k"
385+
>
386+
<path
387+
strokeLinecap="round"
388+
strokeLinejoin="round"
389+
strokeWidth="2"
390+
d="M14 5l7 7m0 0l-7 7m7-7H3"
391+
data-oid="hb-0pvl"
392+
/>
393+
</svg>
394+
<span className="font-medium" data-oid="qktudy9">
395+
I Built This!
396+
</span>
397+
</span>
398+
</button>
399+
400+
<button
401+
onClick={(e) => {
402+
e.stopPropagation();
403+
setIsBountyModalOpen(true);
404+
}}
405+
className="flex-1 md:flex-none group relative inline-flex items-center justify-center overflow-hidden rounded-lg
406+
bg-gradient-to-r from-yellow-400 to-yellow-300 p-[2px] font-medium text-purple-900
407+
shadow-xl shadow-yellow-400/20 transition-all duration-300 hover:shadow-yellow-400/40
408+
hover:scale-[1.02] active:scale-[0.98]"
409+
>
410+
<span
411+
className="relative flex items-center space-x-1.5 rounded-lg bg-gradient-to-r from-yellow-400
412+
to-yellow-300 px-2 py-1.5 text-sm transition-all duration-200 ease-out group-hover:bg-opacity-0
413+
group-hover:from-yellow-300 group-hover:to-yellow-200 w-full justify-center md:justify-start md:w-auto"
414+
>
415+
<svg
416+
className="w-3.5 h-3.5"
417+
fill="none"
418+
stroke="currentColor"
419+
viewBox="0 0 24 24"
420+
>
421+
<path
422+
strokeLinecap="round"
423+
strokeLinejoin="round"
424+
strokeWidth="2"
425+
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
426+
/>
427+
</svg>
428+
<span className="font-medium">Post Bounty</span>
429+
</span>
430+
</button>
431+
</div>
432+
433+
{/* Date and Engagement - Full width on mobile, normal on desktop */}
361434
<div
362-
className="flex items-center space-x-3 text-sm text-purple-300"
435+
className="flex items-center space-x-3 text-sm text-purple-300 order-2 md:order-1"
363436
data-oid="h-_0k9j"
364437
>
365438
<time
@@ -369,7 +442,7 @@ export function BuildRequestCard({ buildRequest }: BuildRequestCardProps) {
369442
>
370443
{formattedDate}
371444
</time>
372-
<span className="hidden md:inline text-purple-600" data-oid="qmdtok:">
445+
<span className="text-purple-600" data-oid="qmdtok:">
373446
374447
</span>
375448
<div className="flex items-center space-x-3" data-oid="0q200ip">
@@ -433,43 +506,6 @@ export function BuildRequestCard({ buildRequest }: BuildRequestCardProps) {
433506
</button>
434507
</div>
435508
</div>
436-
<div className="flex items-center space-x-2">
437-
438-
<button
439-
onClick={handleBuildClick}
440-
className="group relative inline-flex items-center justify-center overflow-hidden rounded-lg
441-
bg-gradient-to-r from-emerald-400 to-emerald-300 p-[2px] font-medium text-emerald-900
442-
shadow-xl shadow-emerald-400/20 transition-all duration-300 hover:shadow-emerald-400/40
443-
hover:scale-[1.02] active:scale-[0.98]"
444-
data-oid="bxp4bi4"
445-
>
446-
<span
447-
className="relative flex items-center space-x-1.5 rounded-lg bg-gradient-to-r from-emerald-400
448-
to-emerald-300 px-2 py-1.5 text-sm transition-all duration-200 ease-out group-hover:bg-opacity-0
449-
group-hover:from-emerald-300 group-hover:to-emerald-200"
450-
data-oid="4ox26cb"
451-
>
452-
<svg
453-
className="w-3.5 h-3.5 transform transition-transform duration-200 group-hover:translate-x-1"
454-
fill="none"
455-
stroke="currentColor"
456-
viewBox="0 0 24 24"
457-
data-oid="_l3tg5k"
458-
>
459-
<path
460-
strokeLinecap="round"
461-
strokeLinejoin="round"
462-
strokeWidth="2"
463-
d="M14 5l7 7m0 0l-7 7m7-7H3"
464-
data-oid="hb-0pvl"
465-
/>
466-
</svg>
467-
<span className="font-medium" data-oid="qktudy9">
468-
I Built This!
469-
</span>
470-
</span>
471-
</button>
472-
</div>
473509
</div>
474510

475511
{/* Auth Modal */}
@@ -491,6 +527,15 @@ export function BuildRequestCard({ buildRequest }: BuildRequestCardProps) {
491527
data-oid="-je2bnc"
492528
/>
493529
</div>
530+
531+
{/* Bounty Modal */}
532+
<div className="relative z-50">
533+
<BountyModal
534+
buildRequest={buildRequest}
535+
isOpen={isBountyModalOpen}
536+
onClose={() => setIsBountyModalOpen(false)}
537+
/>
538+
</div>
494539
</div>
495540
);
496541
}

0 commit comments

Comments
 (0)