From 0e10b7482fd61e43c0cdb9726c03f5404b9be1ac Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Feb 2026 00:34:04 +0000 Subject: [PATCH] Wire contact form, subscribe page, and footer newsletter to send emails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Contact form POSTs to /api/contact → emails Katheryn with reply-to sender - Subscribe page and footer form POST to /api/subscribe → welcome email to subscriber + notification to Katheryn - Loading states, error handling, and disabled buttons while sending Co-Authored-By: Claude Opus 4.6 --- frontend/src/app/api/contact/route.ts | 20 +++++++ frontend/src/app/api/subscribe/route.ts | 20 +++++++ frontend/src/app/contact/page.tsx | 27 ++++++++-- frontend/src/app/subscribe/page.tsx | 28 ++++++++-- frontend/src/components/footer.tsx | 60 +++++++++++++++++---- frontend/src/lib/email.ts | 71 +++++++++++++++++++++++++ 6 files changed, 207 insertions(+), 19 deletions(-) create mode 100644 frontend/src/app/api/contact/route.ts create mode 100644 frontend/src/app/api/subscribe/route.ts diff --git a/frontend/src/app/api/contact/route.ts b/frontend/src/app/api/contact/route.ts new file mode 100644 index 0000000..6880d30 --- /dev/null +++ b/frontend/src/app/api/contact/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { sendContactMessage } from '@/lib/email'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { firstName, lastName, email, subject, message } = body; + + if (!firstName || !lastName || !email || !subject || !message) { + return NextResponse.json({ error: 'All fields are required' }, { status: 400 }); + } + + await sendContactMessage({ firstName, lastName, email, subject, message }); + + return NextResponse.json({ ok: true }); + } catch (err) { + console.error('Contact form error:', err); + return NextResponse.json({ error: 'Failed to send message' }, { status: 500 }); + } +} diff --git a/frontend/src/app/api/subscribe/route.ts b/frontend/src/app/api/subscribe/route.ts new file mode 100644 index 0000000..b037702 --- /dev/null +++ b/frontend/src/app/api/subscribe/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { sendSubscribeNotification } from '@/lib/email'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { email } = body; + + if (!email) { + return NextResponse.json({ error: 'Email is required' }, { status: 400 }); + } + + await sendSubscribeNotification(email); + + return NextResponse.json({ ok: true }); + } catch (err) { + console.error('Subscribe error:', err); + return NextResponse.json({ error: 'Failed to subscribe' }, { status: 500 }); + } +} diff --git a/frontend/src/app/contact/page.tsx b/frontend/src/app/contact/page.tsx index e92a0f3..c15fc9d 100644 --- a/frontend/src/app/contact/page.tsx +++ b/frontend/src/app/contact/page.tsx @@ -15,11 +15,26 @@ function ContactForm() { message: artworkEnquiry ? `I am interested in the artwork "${artworkEnquiry}".` : '', }); const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(''); + const [sending, setSending] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // TODO: Implement form submission (e.g., via API route or email service) - setSubmitted(true); + setSending(true); + setError(''); + try { + const res = await fetch('/api/contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(formData), + }); + if (!res.ok) throw new Error('Failed to send'); + setSubmitted(true); + } catch { + setError('Something went wrong. Please try again or email post@ktrenshaw.com directly.'); + } finally { + setSending(false); + } }; if (submitted) { @@ -137,8 +152,12 @@ function ContactForm() { /> - diff --git a/frontend/src/app/subscribe/page.tsx b/frontend/src/app/subscribe/page.tsx index 6b100dc..5fcf9c9 100644 --- a/frontend/src/app/subscribe/page.tsx +++ b/frontend/src/app/subscribe/page.tsx @@ -5,11 +5,26 @@ import { useState } from 'react'; export default function SubscribePage() { const [email, setEmail] = useState(''); const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(''); + const [sending, setSending] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // TODO: Connect to newsletter API - setSubmitted(true); + setSending(true); + setError(''); + try { + const res = await fetch('/api/subscribe', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email }), + }); + if (!res.ok) throw new Error('Failed to subscribe'); + setSubmitted(true); + } catch { + setError('Something went wrong. Please try again.'); + } finally { + setSending(false); + } }; if (submitted) { @@ -56,11 +71,16 @@ export default function SubscribePage() { /> + {error && ( +

{error}

+ )} +

diff --git a/frontend/src/components/footer.tsx b/frontend/src/components/footer.tsx index 50bb71a..0f826fe 100644 --- a/frontend/src/components/footer.tsx +++ b/frontend/src/components/footer.tsx @@ -1,28 +1,66 @@ -import Link from 'next/link'; +'use client'; + +import Link from 'next/link'; +import { useState } from 'react'; + +function FooterNewsletter() { + const [email, setEmail] = useState(''); + const [status, setStatus] = useState<'idle' | 'sending' | 'done' | 'error'>('idle'); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setStatus('sending'); + try { + const res = await fetch('/api/subscribe', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email }), + }); + if (!res.ok) throw new Error('Failed'); + setStatus('done'); + setEmail(''); + } catch { + setStatus('error'); + } + }; -export function Footer() { return ( -