fix: require email on registration, BCC contact@ on confirmations, update OG image

- Add required email field to registration form (was missing entirely,
  causing "N/A" emails in booking notifications)
- Pass email through full chain: form → API → Google Sheet → Mollie
  metadata → webhook (with Mollie billingAddress as fallback)
- BCC contact@cryptocommonsgather.ing on all payment confirmation
  emails so team is notified of every successful registration
- Replace OG image with alpine mountain card including event name,
  dates, and URL for better Twitter/social sharing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-10 10:58:46 -07:00
parent 45b94e4ed2
commit eaf2c0ac31
6 changed files with 25 additions and 3 deletions

View File

@ -53,6 +53,7 @@ export async function POST(request: NextRequest) {
const metadata: Record<string, string> = {} const metadata: Record<string, string> = {}
if (registrationData) { if (registrationData) {
metadata.name = registrationData.name || "" metadata.name = registrationData.name || ""
metadata.email = registrationData.email || ""
metadata.contact = registrationData.contact || "" metadata.contact = registrationData.contact || ""
metadata.contributions = (registrationData.contributions || "").substring(0, 500) metadata.contributions = (registrationData.contributions || "").substring(0, 500)
metadata.expectations = (registrationData.expectations || "").substring(0, 500) metadata.expectations = (registrationData.expectations || "").substring(0, 500)

View File

@ -5,10 +5,10 @@ export async function POST(request: NextRequest) {
try { try {
const body = await request.json() const body = await request.json()
const { name, contact, contributions, expectations, howHeard, dietary, crewConsent } = body const { name, email, contact, contributions, expectations, howHeard, dietary, crewConsent } = body
// Validate required fields // Validate required fields
if (!name || !contact || !contributions || !expectations || !crewConsent) { if (!name || !email || !contact || !contributions || !expectations || !crewConsent) {
return NextResponse.json( return NextResponse.json(
{ error: "Missing required fields" }, { error: "Missing required fields" },
{ status: 400 } { status: 400 }
@ -21,6 +21,7 @@ export async function POST(request: NextRequest) {
// Add registration to Google Sheet // Add registration to Google Sheet
const rowNumber = await addRegistration({ const rowNumber = await addRegistration({
name, name,
email,
contact, contact,
contributions, contributions,
expectations, expectations,

View File

@ -33,7 +33,7 @@ export async function POST(request: NextRequest) {
console.log(`[Webhook] Payment ${paymentId} status: ${payment.status}`) console.log(`[Webhook] Payment ${paymentId} status: ${payment.status}`)
if (payment.status === "paid") { if (payment.status === "paid") {
const customerEmail = payment.billingAddress?.email || "" const customerEmail = metadata.email || payment.billingAddress?.email || ""
const amountPaid = `${payment.amount.value}` const amountPaid = `${payment.amount.value}`
const accommodationType = metadata.accommodation || "none" const accommodationType = metadata.accommodation || "none"

View File

@ -20,6 +20,7 @@ export default function RegisterPage() {
const [accommodationType, setAccommodationType] = useState("ch-multi") const [accommodationType, setAccommodationType] = useState("ch-multi")
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: "", name: "",
email: "",
contact: "", contact: "",
contributions: "", contributions: "",
expectations: "", expectations: "",
@ -50,6 +51,7 @@ export default function RegisterPage() {
// Validate required fields // Validate required fields
if ( if (
!formData.name || !formData.name ||
!formData.email ||
!formData.contact || !formData.contact ||
!formData.contributions || !formData.contributions ||
!formData.expectations || !formData.expectations ||
@ -74,6 +76,7 @@ export default function RegisterPage() {
}, },
body: JSON.stringify({ body: JSON.stringify({
name: formData.name, name: formData.name,
email: formData.email,
contact: formData.contact, contact: formData.contact,
contributions: formData.contributions, contributions: formData.contributions,
expectations: formData.expectations, expectations: formData.expectations,
@ -494,6 +497,22 @@ export default function RegisterPage() {
/> />
</div> </div>
{/* Email */}
<div className="space-y-2">
<Label htmlFor="email">Email address *</Label>
<Input
id="email"
type="email"
required
placeholder="your@email.com"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
<p className="text-sm text-muted-foreground">
We&apos;ll send your registration confirmation and event updates here.
</p>
</div>
{/* Contact */} {/* Contact */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="contact">How can we contact you besides via email? *</Label> <Label htmlFor="contact">How can we contact you besides via email? *</Label>

View File

@ -179,6 +179,7 @@ export async function sendPaymentConfirmation(
const info = await transport.sendMail({ const info = await transport.sendMail({
from: EMAIL_FROM, from: EMAIL_FROM,
to: data.email, to: data.email,
bcc: INTERNAL_NOTIFY_EMAIL,
subject: "Registration Confirmed - Crypto Commons Gathering 2026", subject: "Registration Confirmed - Crypto Commons Gathering 2026",
html, html,
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 101 KiB