mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
refactor(frontend): consolidate waitlist modals and align with Figma design
- Merge JoinWaitlistModal into WaitlistDetailModal for unified experience - Add MediaCarousel component supporting videos and images with play overlay - Update WaitlistCard styling to match Figma (rounded-large, line-clamp-5, zinc-800 button) - Update success state with party emoji and Close button per Figma design - Add sticky footer for buttons during modal scroll - Support email input for non-logged-in users Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,161 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
import type { StoreWaitlistEntry } from "@/app/api/__generated__/models/storeWaitlistEntry";
|
||||
import { useSupabaseStore } from "@/lib/supabase/hooks/useSupabaseStore";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { usePostV2AddSelfToTheAgentWaitlist } from "@/app/api/__generated__/endpoints/store/store";
|
||||
import { Check } from "@phosphor-icons/react";
|
||||
|
||||
interface JoinWaitlistModalProps {
|
||||
waitlist: StoreWaitlistEntry;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export function JoinWaitlistModal({
|
||||
waitlist,
|
||||
onClose,
|
||||
onSuccess,
|
||||
}: JoinWaitlistModalProps) {
|
||||
const { user } = useSupabaseStore();
|
||||
const [email, setEmail] = useState("");
|
||||
const [success, setSuccess] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const joinWaitlistMutation = usePostV2AddSelfToTheAgentWaitlist();
|
||||
|
||||
function handleJoin() {
|
||||
joinWaitlistMutation.mutate(
|
||||
{
|
||||
waitlistId: waitlist.waitlistId,
|
||||
data: { email: user ? undefined : email },
|
||||
},
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
if (response.status === 200) {
|
||||
setSuccess(true);
|
||||
toast({
|
||||
title: "You're on the list!",
|
||||
description: `We'll notify you when ${waitlist.name} is ready.`,
|
||||
});
|
||||
|
||||
// Close after a short delay to show success state
|
||||
setTimeout(() => {
|
||||
onSuccess?.();
|
||||
onClose();
|
||||
}, 1500);
|
||||
} else {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
description: "Failed to join waitlist. Please try again.",
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
description: "Failed to join waitlist. Please try again.",
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<Dialog
|
||||
title=""
|
||||
controlled={{
|
||||
isOpen: true,
|
||||
set: async (open) => {
|
||||
if (!open) onClose();
|
||||
},
|
||||
}}
|
||||
onClose={onClose}
|
||||
styling={{ maxWidth: "400px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<div className="flex flex-col items-center justify-center py-8">
|
||||
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-green-100 dark:bg-green-900">
|
||||
<Check
|
||||
className="h-8 w-8 text-green-600 dark:text-green-400"
|
||||
size={32}
|
||||
weight="bold"
|
||||
/>
|
||||
</div>
|
||||
<h2 className="mb-2 text-center text-xl font-semibold">
|
||||
You're on the list!
|
||||
</h2>
|
||||
<p className="text-center text-zinc-500">
|
||||
We'll notify you when {waitlist.name} is ready.
|
||||
</p>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title="Join waitlist"
|
||||
controlled={{
|
||||
isOpen: true,
|
||||
set: async (open) => {
|
||||
if (!open) onClose();
|
||||
},
|
||||
}}
|
||||
onClose={onClose}
|
||||
styling={{ maxWidth: "400px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<p className="mb-4 text-sm text-zinc-500">
|
||||
{user
|
||||
? `Get notified when ${waitlist.name} is ready to use.`
|
||||
: `Enter your email to get notified when ${waitlist.name} is ready.`}
|
||||
</p>
|
||||
|
||||
<div className="py-4">
|
||||
{user ? (
|
||||
<div className="rounded-lg bg-neutral-50 p-4 dark:bg-neutral-800">
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
You'll be notified at:
|
||||
</p>
|
||||
<p className="mt-1 font-medium text-neutral-900 dark:text-neutral-100">
|
||||
{user.email}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Input
|
||||
id="email"
|
||||
label="Email address"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="secondary" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleJoin}
|
||||
loading={joinWaitlistMutation.isPending}
|
||||
disabled={!user && !email}
|
||||
className="bg-neutral-800 text-white hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
||||
>
|
||||
{user ? "Join waitlist" : "Join with email"}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export function WaitlistCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex h-[24rem] w-full max-w-md cursor-pointer flex-col items-start rounded-3xl bg-background transition-all duration-300 hover:shadow-lg dark:hover:shadow-gray-700"
|
||||
className="flex h-[24rem] w-full max-w-md cursor-pointer flex-col items-start rounded-3xl bg-white transition-all duration-300 hover:shadow-lg dark:bg-zinc-900 dark:hover:shadow-gray-700"
|
||||
onClick={onCardClick}
|
||||
data-testid="waitlist-card"
|
||||
role="button"
|
||||
@@ -43,7 +43,7 @@ export function WaitlistCard({
|
||||
}}
|
||||
>
|
||||
{/* Image Section */}
|
||||
<div className="relative aspect-[2/1.2] w-full overflow-hidden rounded-3xl md:aspect-[2.17/1]">
|
||||
<div className="relative aspect-[2/1.2] w-full overflow-hidden rounded-large md:aspect-[2.17/1]">
|
||||
{imageUrl ? (
|
||||
<Image
|
||||
src={imageUrl}
|
||||
@@ -73,7 +73,7 @@ export function WaitlistCard({
|
||||
|
||||
{/* Description */}
|
||||
<div className="mt-2 flex w-full flex-col">
|
||||
<p className="line-clamp-3 text-sm font-normal leading-relaxed text-neutral-600 dark:text-neutral-400">
|
||||
<p className="line-clamp-5 text-sm font-normal leading-relaxed text-neutral-600 dark:text-neutral-400">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
@@ -93,7 +93,7 @@ export function WaitlistCard({
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleJoinClick}
|
||||
className="w-full rounded-full bg-neutral-800 text-white hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
||||
className="w-full rounded-full bg-zinc-800 text-white hover:bg-zinc-700 dark:bg-zinc-700 dark:hover:bg-zinc-600"
|
||||
>
|
||||
Join waitlist
|
||||
</Button>
|
||||
|
||||
@@ -1,27 +1,224 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/__legacy__/ui/carousel";
|
||||
import type { StoreWaitlistEntry } from "@/app/api/__generated__/models/storeWaitlistEntry";
|
||||
import { Check } from "@phosphor-icons/react";
|
||||
import { Check, Play } from "@phosphor-icons/react";
|
||||
import { useSupabaseStore } from "@/lib/supabase/hooks/useSupabaseStore";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { usePostV2AddSelfToTheAgentWaitlist } from "@/app/api/__generated__/endpoints/store/store";
|
||||
|
||||
interface MediaItem {
|
||||
type: "image" | "video";
|
||||
url: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
function MediaCarousel({ waitlist }: { waitlist: StoreWaitlistEntry }) {
|
||||
const [activeVideo, setActiveVideo] = useState<string | null>(null);
|
||||
|
||||
// Build media items array: videos first, then images
|
||||
const mediaItems: MediaItem[] = [
|
||||
...(waitlist.videoUrl
|
||||
? [{ type: "video" as const, url: waitlist.videoUrl, label: "Video" }]
|
||||
: []),
|
||||
...(waitlist.agentOutputDemoUrl
|
||||
? [
|
||||
{
|
||||
type: "video" as const,
|
||||
url: waitlist.agentOutputDemoUrl,
|
||||
label: "Demo",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...waitlist.imageUrls.map((url) => ({ type: "image" as const, url })),
|
||||
];
|
||||
|
||||
if (mediaItems.length === 0) return null;
|
||||
|
||||
// Single item - no carousel needed
|
||||
if (mediaItems.length === 1) {
|
||||
const item = mediaItems[0];
|
||||
return (
|
||||
<div className="relative aspect-[350/196] w-full overflow-hidden rounded-large">
|
||||
{item.type === "image" ? (
|
||||
<Image
|
||||
src={item.url}
|
||||
alt={`${waitlist.name} preview`}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={item.url}
|
||||
controls
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Multiple items - use carousel
|
||||
return (
|
||||
<Carousel className="w-full">
|
||||
<CarouselContent>
|
||||
{mediaItems.map((item, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<div className="relative aspect-[350/196] w-full overflow-hidden rounded-large">
|
||||
{item.type === "image" ? (
|
||||
<Image
|
||||
src={item.url}
|
||||
alt={`${waitlist.name} preview ${index + 1}`}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
) : activeVideo === item.url ? (
|
||||
<video
|
||||
src={item.url}
|
||||
controls
|
||||
autoPlay
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setActiveVideo(item.url)}
|
||||
className="group relative h-full w-full bg-zinc-900"
|
||||
>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-white/90 transition-transform group-hover:scale-110">
|
||||
<Play size={32} weight="fill" className="text-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
<span className="absolute bottom-3 left-3 text-sm text-white">
|
||||
{item.label}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious className="left-2 top-1/2 -translate-y-1/2" />
|
||||
<CarouselNext className="right-2 top-1/2 -translate-y-1/2" />
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
|
||||
interface WaitlistDetailModalProps {
|
||||
waitlist: StoreWaitlistEntry;
|
||||
isMember?: boolean;
|
||||
onClose: () => void;
|
||||
onJoin: () => void;
|
||||
onJoinSuccess?: (waitlistId: string) => void;
|
||||
}
|
||||
|
||||
export function WaitlistDetailModal({
|
||||
waitlist,
|
||||
isMember = false,
|
||||
onClose,
|
||||
onJoin,
|
||||
onJoinSuccess,
|
||||
}: WaitlistDetailModalProps) {
|
||||
const { user } = useSupabaseStore();
|
||||
const [email, setEmail] = useState("");
|
||||
const [success, setSuccess] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const joinWaitlistMutation = usePostV2AddSelfToTheAgentWaitlist();
|
||||
|
||||
function handleJoin() {
|
||||
joinWaitlistMutation.mutate(
|
||||
{
|
||||
waitlistId: waitlist.waitlistId,
|
||||
data: { email: user ? undefined : email },
|
||||
},
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
if (response.status === 200) {
|
||||
setSuccess(true);
|
||||
toast({
|
||||
title: "You're on the waitlist!",
|
||||
description: `We'll notify you when ${waitlist.name} goes live.`,
|
||||
});
|
||||
onJoinSuccess?.(waitlist.waitlistId);
|
||||
} else {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
description: "Failed to join waitlist. Please try again.",
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
description: "Failed to join waitlist. Please try again.",
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Success state
|
||||
if (success) {
|
||||
return (
|
||||
<Dialog
|
||||
title=""
|
||||
controlled={{
|
||||
isOpen: true,
|
||||
set: async (open) => {
|
||||
if (!open) onClose();
|
||||
},
|
||||
}}
|
||||
onClose={onClose}
|
||||
styling={{ maxWidth: "500px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<div className="flex flex-col items-center justify-center py-4 text-center">
|
||||
{/* Party emoji */}
|
||||
<span className="mb-2 text-5xl">🎉</span>
|
||||
|
||||
{/* Title */}
|
||||
<h2 className="mb-2 font-poppins text-[22px] font-medium leading-7 text-zinc-900 dark:text-zinc-100">
|
||||
You're on the waitlist
|
||||
</h2>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p className="text-base leading-[26px] text-zinc-600 dark:text-zinc-400">
|
||||
Thanks for helping us prioritize which agents to build next.
|
||||
We'll notify you when this agent goes live in the
|
||||
marketplace.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Close button */}
|
||||
<Dialog.Footer className="flex justify-center pb-2 pt-4">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
className="rounded-full border border-zinc-700 bg-white px-4 py-3 text-zinc-900 hover:bg-zinc-100 dark:border-zinc-500 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// Main modal - handles both member and non-member states
|
||||
return (
|
||||
<Dialog
|
||||
title={waitlist.name}
|
||||
title="Join the waitlist"
|
||||
controlled={{
|
||||
isOpen: true,
|
||||
set: async (open) => {
|
||||
@@ -29,106 +226,74 @@ export function WaitlistDetailModal({
|
||||
},
|
||||
}}
|
||||
onClose={onClose}
|
||||
styling={{ maxWidth: "700px" }}
|
||||
styling={{ maxWidth: "500px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<div className="space-y-6">
|
||||
{/* Main Image */}
|
||||
{waitlist.imageUrls.length > 0 && (
|
||||
<div className="relative aspect-video w-full overflow-hidden rounded-xl">
|
||||
<Image
|
||||
src={waitlist.imageUrls[0]}
|
||||
alt={`${waitlist.name} preview`}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Subtitle */}
|
||||
<p className="mb-6 text-center text-base text-zinc-600 dark:text-zinc-400">
|
||||
Help us decide what to build next — and get notified when this agent
|
||||
is ready
|
||||
</p>
|
||||
|
||||
{/* Subheading */}
|
||||
<p className="text-lg font-medium text-neutral-700 dark:text-neutral-300">
|
||||
{waitlist.subHeading}
|
||||
</p>
|
||||
{/* Media Carousel */}
|
||||
<MediaCarousel waitlist={waitlist} />
|
||||
|
||||
{/* Description */}
|
||||
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||
<p className="whitespace-pre-wrap text-neutral-600 dark:text-neutral-400">
|
||||
{waitlist.description}
|
||||
</p>
|
||||
{/* Agent Name */}
|
||||
<h3 className="mt-4 font-poppins text-[22px] font-medium leading-7 text-zinc-800 dark:text-zinc-100">
|
||||
{waitlist.name}
|
||||
</h3>
|
||||
|
||||
{/* Agent Description */}
|
||||
<p className="mt-2 line-clamp-5 text-sm leading-[22px] text-zinc-500 dark:text-zinc-400">
|
||||
{waitlist.description}
|
||||
</p>
|
||||
|
||||
{/* Email input for non-logged-in users who haven't joined */}
|
||||
{!isMember && !user && (
|
||||
<div className="mt-4 pr-1">
|
||||
<Input
|
||||
id="email"
|
||||
label="Email address"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Video */}
|
||||
{waitlist.videoUrl && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium text-neutral-800 dark:text-neutral-200">
|
||||
Video
|
||||
</h4>
|
||||
<div className="relative aspect-video w-full overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-800">
|
||||
<iframe
|
||||
src={waitlist.videoUrl}
|
||||
title={`${waitlist.name} video`}
|
||||
className="h-full w-full"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Output Demo */}
|
||||
{waitlist.agentOutputDemoUrl && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium text-neutral-800 dark:text-neutral-200">
|
||||
Output Demo
|
||||
</h4>
|
||||
<div className="relative aspect-video w-full overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-800">
|
||||
<video
|
||||
src={waitlist.agentOutputDemoUrl}
|
||||
controls
|
||||
className="h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Categories */}
|
||||
{waitlist.categories.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium text-neutral-800 dark:text-neutral-200">
|
||||
Categories
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{waitlist.categories.map((category, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="rounded-full bg-neutral-100 px-3 py-1 text-sm text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300"
|
||||
>
|
||||
{category}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Join Button */}
|
||||
<Dialog.Footer>
|
||||
{isMember ? (
|
||||
{/* Footer buttons */}
|
||||
<Dialog.Footer className="sticky bottom-0 mt-6 flex justify-center gap-3 bg-white pb-2 pt-4 dark:bg-zinc-900">
|
||||
{isMember ? (
|
||||
<Button
|
||||
disabled
|
||||
className="rounded-full bg-green-600 px-4 py-3 text-white hover:bg-green-600 dark:bg-green-700 dark:hover:bg-green-700"
|
||||
>
|
||||
<Check size={16} className="mr-2" />
|
||||
You're on the waitlist
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
disabled
|
||||
className="w-full rounded-full bg-green-600 text-white hover:bg-green-600 dark:bg-green-700 dark:hover:bg-green-700"
|
||||
>
|
||||
<Check size={16} className="mr-2" />
|
||||
You're on the waitlist
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={onJoin}
|
||||
className="w-full rounded-full bg-neutral-800 text-white hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
||||
onClick={handleJoin}
|
||||
loading={joinWaitlistMutation.isPending}
|
||||
disabled={!user && !email}
|
||||
className="rounded-full bg-zinc-800 px-4 py-3 text-white hover:bg-zinc-700 dark:bg-zinc-700 dark:hover:bg-zinc-600"
|
||||
>
|
||||
Join waitlist
|
||||
</Button>
|
||||
)}
|
||||
</Dialog.Footer>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
className="rounded-full bg-zinc-200 px-4 py-3 text-zinc-900 hover:bg-zinc-300 dark:bg-zinc-700 dark:text-zinc-100 dark:hover:bg-zinc-600"
|
||||
>
|
||||
Not now
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from "@/components/__legacy__/ui/carousel";
|
||||
import { WaitlistCard } from "../WaitlistCard/WaitlistCard";
|
||||
import { WaitlistDetailModal } from "../WaitlistDetailModal/WaitlistDetailModal";
|
||||
import { JoinWaitlistModal } from "../JoinWaitlistModal/JoinWaitlistModal";
|
||||
import type { StoreWaitlistEntry } from "@/app/api/__generated__/models/storeWaitlistEntry";
|
||||
import { useWaitlistSection } from "./useWaitlistSection";
|
||||
|
||||
@@ -17,27 +16,13 @@ export function WaitlistSection() {
|
||||
useWaitlistSection();
|
||||
const [selectedWaitlist, setSelectedWaitlist] =
|
||||
useState<StoreWaitlistEntry | null>(null);
|
||||
const [joiningWaitlist, setJoiningWaitlist] =
|
||||
useState<StoreWaitlistEntry | null>(null);
|
||||
|
||||
function handleCardClick(waitlist: StoreWaitlistEntry) {
|
||||
function handleOpenModal(waitlist: StoreWaitlistEntry) {
|
||||
setSelectedWaitlist(waitlist);
|
||||
}
|
||||
|
||||
function handleJoinClick(waitlist: StoreWaitlistEntry) {
|
||||
setJoiningWaitlist(waitlist);
|
||||
}
|
||||
|
||||
function handleJoinFromDetail() {
|
||||
if (selectedWaitlist) {
|
||||
setJoiningWaitlist(selectedWaitlist);
|
||||
setSelectedWaitlist(null);
|
||||
}
|
||||
}
|
||||
|
||||
function handleJoinSuccess(waitlistId: string) {
|
||||
markAsJoined(waitlistId);
|
||||
setJoiningWaitlist(null);
|
||||
}
|
||||
|
||||
// Don't render if loading, error, or no waitlists
|
||||
@@ -78,8 +63,8 @@ export function WaitlistSection() {
|
||||
description={waitlist.description}
|
||||
imageUrl={waitlist.imageUrls[0] || null}
|
||||
isMember={joinedWaitlistIds.has(waitlist.waitlistId)}
|
||||
onCardClick={() => handleCardClick(waitlist)}
|
||||
onJoinClick={() => handleJoinClick(waitlist)}
|
||||
onCardClick={() => handleOpenModal(waitlist)}
|
||||
onJoinClick={() => handleOpenModal(waitlist)}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
@@ -96,29 +81,20 @@ export function WaitlistSection() {
|
||||
description={waitlist.description}
|
||||
imageUrl={waitlist.imageUrls[0] || null}
|
||||
isMember={joinedWaitlistIds.has(waitlist.waitlistId)}
|
||||
onCardClick={() => handleCardClick(waitlist)}
|
||||
onJoinClick={() => handleJoinClick(waitlist)}
|
||||
onCardClick={() => handleOpenModal(waitlist)}
|
||||
onJoinClick={() => handleOpenModal(waitlist)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Detail Modal */}
|
||||
{/* Single Modal for both viewing and joining */}
|
||||
{selectedWaitlist && (
|
||||
<WaitlistDetailModal
|
||||
waitlist={selectedWaitlist}
|
||||
isMember={joinedWaitlistIds.has(selectedWaitlist.waitlistId)}
|
||||
onClose={() => setSelectedWaitlist(null)}
|
||||
onJoin={handleJoinFromDetail}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Join Modal */}
|
||||
{joiningWaitlist && (
|
||||
<JoinWaitlistModal
|
||||
waitlist={joiningWaitlist}
|
||||
onClose={() => setJoiningWaitlist(null)}
|
||||
onSuccess={() => handleJoinSuccess(joiningWaitlist.waitlistId)}
|
||||
onJoinSuccess={handleJoinSuccess}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user