diff --git a/app/layout.tsx b/app/layout.tsx index 23f99cb..f884bda 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -12,6 +12,9 @@ export const metadata: Metadata = { description: "A distributed, local-first, collaborative digital ecosystem. Access the alternative internet through physical USB keys and local mesh networks.", generator: "v0.app", + icons: { + icon: "data:image/svg+xml,🧅", + }, } export default function RootLayout({ diff --git a/app/page.tsx b/app/page.tsx index 5c9905c..56aec39 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,6 +2,7 @@ import { Hero } from "@/components/hero" import { Features } from "@/components/features" import { HowItWorks } from "@/components/how-it-works" import { Philosophy } from "@/components/philosophy" +import { NewsletterSignup } from "@/components/newsletter-signup" import { Footer } from "@/components/footer" export default function Page() { @@ -11,6 +12,7 @@ export default function Page() { + ) diff --git a/components/newsletter-signup.tsx b/components/newsletter-signup.tsx new file mode 100644 index 0000000..4d62c1a --- /dev/null +++ b/components/newsletter-signup.tsx @@ -0,0 +1,95 @@ +"use client" + +import { useState } from "react" +import { Button } from "@/components/ui/button" + +const LISTMONK_URL = "https://newsletter.jeffemmett.com" +const LIST_UUID = "0c541da4-3f04-4e72-978a-5a328b43c995" // Alltornet list + +export function NewsletterSignup() { + const [email, setEmail] = useState("") + const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle") + const [message, setMessage] = useState("") + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!email) return + + setStatus("loading") + + try { + const response = await fetch(`${LISTMONK_URL}/subscription/form`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + email: email, + list: LIST_UUID, + name: "", + }), + }) + + if (response.ok) { + setStatus("success") + setMessage("Check your email to confirm your subscription!") + setEmail("") + } else { + throw new Error("Subscription failed") + } + } catch { + setStatus("error") + setMessage("Something went wrong. Please try again.") + } + } + + return ( + + + + + + Stay in the Loop + + + Subscribe for updates from Alltornet. + + + + {status === "success" ? ( + + {message} + + ) : ( + + setEmail(e.target.value)} + required + className="flex-1 px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary/50" + /> + + {status === "loading" ? "Subscribing..." : "Subscribe"} + + + )} + + {status === "error" && ( + {message} + )} + + + No spam, unsubscribe anytime. We respect your privacy. + + + + + ) +}
+ Subscribe for updates from Alltornet. +
{message}
+ No spam, unsubscribe anytime. We respect your privacy. +