diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-06-22 14:25:19 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-06-22 14:25:19 +0200 |
| commit | e84caf3f9fbdf69e31b4dd7df3ca512108816d5d (patch) | |
| tree | 12bb8b404823729c1f8f026a81c2abbad0539d7a /apps/web | |
| parent | 6e84dcaffbf616c39f82dee70a57a51f06a22b0e (diff) | |
| download | opencut-e84caf3f9fbdf69e31b4dd7df3ca512108816d5d.tar.gz | |
feat: add waitlist signup count
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/src/app/page.tsx | 7 | ||||
| -rw-r--r-- | apps/web/src/components/landing/hero.tsx | 38 | ||||
| -rw-r--r-- | apps/web/src/lib/waitlist.ts | 15 |
3 files changed, 52 insertions, 8 deletions
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 57967b7a..614aae3a 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,11 +1,14 @@ import { Hero } from "@/components/landing/hero";
import { Header } from "@/components/header";
+import { getWaitlistCount } from "@/lib/waitlist";
+
+export default async function Home() {
+ const signupCount = await getWaitlistCount();
-export default function Home() {
return (
<div>
<Header />
- <Hero />
+ <Hero signupCount={signupCount} />
</div>
);
}
diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index e5eca8b6..f970179b 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -6,18 +6,25 @@ import { Input } from "../ui/input"; import { ArrowRight } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
-import { toast } from "sonner";
+import { useToast } from "@/hooks/use-toast";
-export function Hero() {
+interface HeroProps {
+ signupCount: number;
+}
+
+export function Hero({ signupCount }: HeroProps) {
const [email, setEmail] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
+ const { toast } = useToast();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email.trim()) {
- toast.error("Email required", {
+ toast({
+ title: "Email required",
description: "Please enter your email address.",
+ variant: "destructive",
});
return;
}
@@ -36,16 +43,23 @@ export function Hero() { const data = await response.json();
if (response.ok) {
- toast.success("Welcome to the waitlist! 🎉");
+ toast({
+ title: "Welcome to the waitlist! 🎉",
+ description: "You'll be notified when we launch.",
+ });
setEmail("");
} else {
- toast.error("Oops!", {
+ toast({
+ title: "Oops!",
description: data.error || "Something went wrong. Please try again.",
+ variant: "destructive",
});
}
} catch (error) {
- toast.error("Network error", {
+ toast({
+ title: "Network error",
description: "Please check your connection and try again.",
+ variant: "destructive",
});
} finally {
setIsSubmitting(false);
@@ -113,6 +127,18 @@ export function Hero() { </Button>
</form>
</motion.div>
+
+ {signupCount > 0 && (
+ <motion.div
+ initial={{ opacity: 0, y: 10 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={{ delay: 0.8, duration: 0.6 }}
+ className="mt-6 inline-flex items-center gap-2 bg-muted/30 px-4 py-2 rounded-full text-sm text-muted-foreground"
+ >
+ <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
+ <span>{signupCount.toLocaleString()} people already joined</span>
+ </motion.div>
+ )}
</motion.div>
<motion.div
diff --git a/apps/web/src/lib/waitlist.ts b/apps/web/src/lib/waitlist.ts new file mode 100644 index 00000000..c397109d --- /dev/null +++ b/apps/web/src/lib/waitlist.ts @@ -0,0 +1,15 @@ +import { db } from "@/lib/db";
+import { waitlist } from "@/lib/db/schema";
+import { sql } from "drizzle-orm";
+
+export async function getWaitlistCount() {
+ try {
+ const result = await db
+ .select({ count: sql<number>`count(*)` })
+ .from(waitlist);
+ return result[0]?.count || 0;
+ } catch (error) {
+ console.error("Failed to fetch waitlist count:", error);
+ return 0;
+ }
+}
|
