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:
parent
832d464c7d
commit
c2480ee3c9
|
|
@ -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!"
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
1745
pnpm-lock.yaml
1745
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue