diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index e9957e1..183b6a5 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,25 +1,27 @@ -import { Hero } from "@/components/landing/hero"; -import { Header } from "@/components/header"; -import { getWaitlistCount } from "@/lib/waitlist"; -import Image from "next/image"; - -// Force dynamic rendering so waitlist count updates in real-time -export const dynamic = "force-dynamic"; - -export default async function Home() { - const signupCount = await getWaitlistCount(); - - return ( -
- landing-page.bg -
- -
- ); -} +import { Hero } from "@/components/landing/hero"; +import { Header } from "@/components/header"; +import { Footer } from "@/components/footer"; +import { getWaitlistCount } from "@/lib/waitlist"; +import Image from "next/image"; + +// Force dynamic rendering so waitlist count updates in real-time +export const dynamic = "force-dynamic"; + +export default async function Home() { + const signupCount = await getWaitlistCount(); + + return ( +
+ landing-page.bg +
+ +
+ ); +} diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx new file mode 100644 index 0000000..7cc1440 --- /dev/null +++ b/apps/web/src/components/footer.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { motion } from "motion/react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { RiGithubLine, RiTwitterXLine } from "react-icons/ri"; +import { getStars } from "@/lib/fetchGhStars"; +import Image from "next/image"; + +export function Footer() { + const [star, setStar] = useState(); + + useEffect(() => { + const fetchStars = async () => { + try { + const data = await getStars(); + setStar(data); + } catch (err) { + console.error("Failed to fetch GitHub stars", err); + } + }; + + fetchStars(); + }, []); + + return ( + +
+
+ {/* Brand Section */} +
+
+ OpenCut + OpenCut +
+

+ The open source video editor that gets the job done. Simple, + powerful, and works on any platform. +

+
+ + + + + + +
+
+ +
+
+

Resources

+
    +
  • + + Privacy policy + +
  • +
  • + + Terms of use + +
  • +
+
+ + {/* Company Links */} +
+

Company

+
    +
  • + + Contributors + +
  • +
  • + + About + +
  • +
+
+
+
+ + {/* Bottom Section */} +
+
+ © 2025 OpenCut, All Rights Reserved +
+
+
+
+ ); +} diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index 7f4a7f2..1ac9390 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -1,197 +1,153 @@ -"use client"; - -import { motion } from "motion/react"; -import { Button } from "../ui/button"; -import { Input } from "../ui/input"; -import { ArrowRight } from "lucide-react"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useToast } from "@/hooks/use-toast"; -import { getStars } from "@/lib/fetchGhStars"; -import Image from "next/image"; -import { RiGithubLine, RiTwitterXLine } from "react-icons/ri"; - -interface HeroProps { - signupCount: number; -} - -export function Hero({ signupCount }: HeroProps) { - const [star, setStar] = useState(); - const [email, setEmail] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - const { toast } = useToast(); - - useEffect(() => { - const fetchStars = async () => { - try { - const data = await getStars(); - setStar(data); - } catch (err) { - console.error("Failed to fetch GitHub stars", err); - } - }; - - fetchStars(); - }, []); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!email.trim()) { - toast({ - title: "Email required", - description: "Please enter your email address.", - variant: "destructive", - }); - return; - } - - setIsSubmitting(true); - - try { - const response = await fetch("/api/waitlist", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ email: email.trim() }), - }); - - const data = await response.json(); - - if (response.ok) { - toast({ - title: "Welcome to the waitlist! 🎉", - description: "You'll be notified when we launch.", - }); - setEmail(""); - } else { - toast({ - title: "Oops!", - description: data.error || "Something went wrong. Please try again.", - variant: "destructive", - }); - } - } catch (error) { - toast({ - title: "Network error", - description: "Please check your connection and try again.", - variant: "destructive", - }); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
- - -

The Open Source

-
-
- frame - - Video Editor - -
-
-
- - - A simple but powerful video editor that gets the job done. Works on - any platform. - - - -
- setEmail(e.target.value)} - disabled={isSubmitting} - required - /> - -
-
- - {signupCount > 0 && ( - -
- {signupCount.toLocaleString()} people already joined - - )} - - - - Currently in beta • Open source on{" "} - - Github - - {star}+ - - • Follow us on - - Twitter - - - -
- ); -} +"use client"; + +import { motion } from "motion/react"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { ArrowRight } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { useToast } from "@/hooks/use-toast"; + +import Image from "next/image"; + +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({ + title: "Email required", + description: "Please enter your email address.", + variant: "destructive", + }); + return; + } + + setIsSubmitting(true); + + try { + const response = await fetch("/api/waitlist", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email: email.trim() }), + }); + + const data = await response.json(); + + if (response.ok) { + toast({ + title: "Welcome to the waitlist! 🎉", + description: "You'll be notified when we launch.", + }); + setEmail(""); + } else { + toast({ + title: "Oops!", + description: data.error || "Something went wrong. Please try again.", + variant: "destructive", + }); + } + } catch (error) { + toast({ + title: "Network error", + description: "Please check your connection and try again.", + variant: "destructive", + }); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+ + +

The Open Source

+
+
+ frame + + Video Editor + +
+
+
+ + + A simple but powerful video editor that gets the job done. Works on + any platform. + + + +
+ setEmail(e.target.value)} + disabled={isSubmitting} + required + /> + +
+
+ + {signupCount > 0 && ( + +
+ {signupCount.toLocaleString()} people already joined + + )} + +
+ ); +} diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts index c35ee1a..00309f8 100644 --- a/apps/web/tailwind.config.ts +++ b/apps/web/tailwind.config.ts @@ -69,7 +69,7 @@ export default { borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", + sm: "calc(var(--radius) - 6px)", }, keyframes: { "accordion-down": {