feat: add newsletter signup component and improve mirror workflow

- Add NewsletterSignup component with Listmonk integration
- Update Footer to accept className prop
- Fix GitHub→Gitea mirror workflow with better error handling
- Update Next.js to 15.5.9

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-22 23:05:03 -05:00
parent 832d464c7d
commit c2480ee3c9
6 changed files with 995 additions and 892 deletions

View File

@ -16,13 +16,35 @@ jobs:
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
- name: Mirror to Gitea
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_USERNAME: ${{ secrets.GITEA_USERNAME }}
run: |
# Validate secrets are set
if [ -z "$GITEA_TOKEN" ] || [ -z "$GITEA_USERNAME" ]; then
echo "Error: GITEA_TOKEN or GITEA_USERNAME secrets not set"
exit 1
fi
REPO_NAME=$(basename $GITHUB_REPOSITORY)
git remote add gitea https://$GITEA_USERNAME:$GITEA_TOKEN@gitea.jeffemmett.com/jeffemmett/$REPO_NAME.git || true
GITEA_URL="https://${GITEA_USERNAME}:${GITEA_TOKEN}@gitea.jeffemmett.com/jeffemmett/${REPO_NAME}.git"
# Remove existing remote if present, then add fresh
git remote remove gitea 2>/dev/null || true
git remote add gitea "$GITEA_URL"
# Push with verbose output for debugging
echo "Pushing all branches to Gitea..."
git push gitea --all --force
echo "Pushing tags to Gitea..."
git push gitea --tags --force
echo "Mirror complete!"

View File

@ -6,6 +6,7 @@ import { ImageGallery } from "@/components/image-gallery"
import { ContentSection } from "@/components/content-section"
import { MycopunkSection } from "@/components/mycopunk-section"
import { CTASection } from "@/components/cta-section"
import { NewsletterSignup } from "@/components/newsletter-signup"
import { Footer } from "@/components/footer"
import { HyphalCanvas } from "@/components/hyphal-canvas"
@ -22,6 +23,7 @@ export default function Home() {
<ImageGallery />
<MycopunkSection />
<CTASection />
<NewsletterSignup />
</main>
<Footer className="z-20" />
</div>

View File

@ -1,9 +1,13 @@
import Link from "next/link"
import { Sprout } from "lucide-react"
export function Footer() {
interface FooterProps {
className?: string
}
export function Footer({ className }: FooterProps) {
return (
<footer className="border-t border-border bg-white relative z-20">
<footer className={`border-t border-border bg-white relative z-20 ${className || ""}`}>
<div className="container mx-auto px-4 py-12">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div className="md:col-span-2">

View File

@ -0,0 +1,106 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Sprout } from "lucide-react"
const LISTMONK_URL = "https://newsletter.jeffemmett.com"
const LIST_UUID = "d076325f-f39a-44a2-874d-2026c7eb6d1c" // MycoFi 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}/api/public/subscription`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
list_uuids: [LIST_UUID],
name: "",
}),
})
if (response.ok) {
setStatus("success")
setMessage("Merge in to the mesh.")
setEmail("")
} else {
throw new Error("Subscription failed")
}
} catch {
setStatus("error")
setMessage("Something went wrong. Please try again.")
}
}
return (
<section className="py-16 bg-gradient-to-b from-background to-emerald-50/30 relative z-10">
<div className="container mx-auto px-4">
<div className="max-w-xl mx-auto text-center space-y-6">
<div className="inline-flex items-center gap-2 rounded-full bg-emerald-50 px-4 py-2 text-sm text-black">
<Sprout className="h-4 w-4 text-secondary" />
<span>Stay Connected</span>
</div>
<div className="space-y-2">
<h2
className="text-3xl font-bold"
style={{ fontFamily: "var(--font-crimson)" }}
>
Join the Mycelial Network
</h2>
<p className="text-muted-foreground">
Subscribe for updates on regenerative economics, mycoeconomics research,
and building regenerative futures.
</p>
</div>
{status === "success" ? (
<div className="p-4 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-emerald-700">
{message}
</div>
) : (
<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3">
<Input
type="email"
placeholder="your@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="flex-1"
/>
<Button
type="submit"
disabled={status === "loading"}
className="px-6"
>
{status === "loading" ? "Subscribing..." : "Subscribe"}
</Button>
</form>
)}
{status === "error" && (
<p className="text-sm text-red-500">{message}</p>
)}
<p className="text-xs text-muted-foreground">
No spam, unsubscribe anytime. We respect your privacy.
</p>
</div>
</div>
</section>
)
}

View File

@ -48,7 +48,7 @@
"geist": "latest",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"next": "15.5.4",
"next": "15.5.9",
"next-themes": "^0.4.6",
"react": "19.1.0",
"react-day-picker": "9.8.0",

File diff suppressed because it is too large Load Diff