Files
wiki/src/components/NewsletterSignup.tsx
Wayne Sutton a87db9d171 docs: add changelog entries for v1.33.1 through v1.37.0
Add missing changelog entries to content/pages/changelog-page.md:

v1.34.0 (2025-12-26): Blog page featured layout with hero post
- blogFeatured frontmatter field for posts
- Hero card displays first featured post with landscape image
- 2-column featured row for remaining featured posts
- 3-column grid for regular posts

v1.35.0 (2025-12-26): Image support at top of posts and pages
- showImageAtTop frontmatter field
- Full-width image display above post header
- Works for both posts and pages

v1.36.0 (2025-12-27): Social footer component
- Customizable social links (8 platform types)
- Copyright with auto-updating year
- showSocialFooter frontmatter field for per-page control
- Configurable via siteConfig.socialFooter

v1.37.0 (2025-12-27): Newsletter Admin UI
- Three-column admin interface at /newsletter-admin
- Subscriber management with search and filters
- Send newsletter panel (post selection or custom email)
- Weekly digest automation (Sunday 9am UTC)
- Developer notifications (subscriber alerts, weekly stats)
- Markdown-to-HTML conversion for custom emails
2025-12-27 15:32:07 -08:00

117 lines
3.5 KiB
TypeScript

import { useState } from "react";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import siteConfig from "../config/siteConfig";
// Props for the newsletter signup component
interface NewsletterSignupProps {
source: "home" | "blog-page" | "post"; // Where the signup form appears
postSlug?: string; // For tracking which post they subscribed from
title?: string; // Override default title
description?: string; // Override default description
}
// Newsletter signup component
// Displays email input form for newsletter subscriptions
// Integrates with Convex backend for subscriber management
export default function NewsletterSignup({
source,
postSlug,
title,
description,
}: NewsletterSignupProps) {
const [email, setEmail] = useState("");
const [status, setStatus] = useState<
"idle" | "loading" | "success" | "error"
>("idle");
const [message, setMessage] = useState("");
const subscribe = useMutation(api.newsletter.subscribe);
// Check if newsletter is enabled globally
if (!siteConfig.newsletter?.enabled) return null;
// Get config for this placement
const config =
source === "home"
? siteConfig.newsletter.signup.home
: source === "blog-page"
? siteConfig.newsletter.signup.blogPage
: siteConfig.newsletter.signup.posts;
// Check if this specific placement is enabled
if (!config.enabled) return null;
const displayTitle = title || config.title;
const displayDescription = description || config.description;
// Handle form submission
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email.trim()) {
setStatus("error");
setMessage("Please enter your email.");
return;
}
setStatus("loading");
try {
// Include post slug in source for tracking
const sourceValue = postSlug ? `post:${postSlug}` : source;
const result = await subscribe({ email, source: sourceValue });
if (result.success) {
setStatus("success");
setMessage(result.message);
setEmail("");
} else {
setStatus("error");
setMessage(result.message);
}
} catch {
setStatus("error");
setMessage("Something went wrong. Please try again.");
}
};
return (
<section className="newsletter-signup">
<div className="newsletter-signup__content">
<h3 className="newsletter-signup__title">{displayTitle}</h3>
{displayDescription && (
<p className="newsletter-signup__description">{displayDescription}</p>
)}
{status === "success" ? (
<p className="newsletter-signup__success">{message}</p>
) : (
<form onSubmit={handleSubmit} className="newsletter-signup__form">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
className="newsletter-signup__input"
disabled={status === "loading"}
aria-label="Email address"
/>
<button
type="submit"
className="newsletter-signup__button"
disabled={status === "loading"}
>
{status === "loading" ? "..." : "Subscribe"}
</button>
</form>
)}
{status === "error" && (
<p className="newsletter-signup__error">{message}</p>
)}
</div>
</section>
);
}