Add contact form and thank-you redirect flow

This commit is contained in:
nmemmert
2026-04-09 14:28:36 -04:00
parent 5b401671db
commit e93840c25f
2 changed files with 248 additions and 2 deletions
+170 -1
View File
@@ -491,6 +491,143 @@
padding: 0; padding: 0;
} }
/* ── Contact ── */
.section-contact {
background: #090909;
border-top: 1px solid rgba(200, 134, 10, 0.18);
border-bottom: 1px solid rgba(200, 134, 10, 0.18);
padding: 5rem 0;
}
.contact-inner {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2.5rem;
align-items: start;
}
.contact-copy .section-heading {
text-align: left;
margin-bottom: 1rem;
}
.contact-copy p {
font-family: 'Barlow Condensed', sans-serif;
font-weight: 300;
font-size: 1.08rem;
line-height: 1.7;
color: #a89060;
margin: 0;
padding: 0;
}
.contact-form {
background: #121212;
border: 1px solid rgba(200, 134, 10, 0.22);
border-radius: 14px;
padding: 1.4rem;
display: flex;
flex-direction: column;
gap: 0.95rem;
}
.contact-form label {
display: flex;
flex-direction: column;
gap: 0.4rem;
font-family: 'Barlow Condensed', sans-serif;
font-weight: 500;
font-size: 0.82rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: #c8860a;
}
.contact-form input,
.contact-form textarea {
background: #0c0c0c;
border: 1px solid rgba(200, 134, 10, 0.25);
border-radius: 8px;
color: #f0e6d0;
font-family: 'Barlow Condensed', sans-serif;
font-size: 1rem;
font-weight: 300;
padding: 0.7rem 0.85rem;
outline: none;
transition: border-color 200ms;
}
.contact-form input:focus,
.contact-form textarea:focus {
border-color: rgba(200, 134, 10, 0.7);
}
.contact-form textarea {
resize: vertical;
}
.contact-form .btn-primary {
justify-content: center;
width: 100%;
margin-top: 0.25rem;
}
.contact-honeypot {
position: absolute;
left: -9999px;
opacity: 0;
pointer-events: none;
}
/* ── Thank You Page ── */
.thanks-page {
min-height: 100vh;
background:
radial-gradient(ellipse at top, rgba(200, 134, 10, 0.12) 0%, transparent 55%),
#0a0a0a;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.thanks-card {
width: min(720px, 100%);
background: #121212;
border: 1px solid rgba(200, 134, 10, 0.24);
border-radius: 16px;
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
padding: 2rem;
text-align: center;
}
.thanks-card h1 {
font-family: 'Playfair Display', Georgia, serif;
font-size: clamp(2rem, 5vw, 3rem);
color: #f0e6d0;
margin: 0.3rem 0 1rem;
}
.thanks-card p {
font-family: 'Barlow Condensed', sans-serif;
font-size: 1.1rem;
font-weight: 300;
line-height: 1.7;
color: #a89060;
margin: 0 0 1rem;
padding: 0;
}
.thanks-countdown {
color: #d4b87a !important;
letter-spacing: 0.06em;
margin-bottom: 1.3rem !important;
}
.thanks-card .btn-primary {
justify-content: center;
}
/* ── Footer ── */ /* ── Footer ── */
.site-footer { .site-footer {
background: #050505; background: #050505;
@@ -529,6 +666,28 @@
padding: 0; padding: 0;
} }
.footer-contact {
font-family: 'Barlow Condensed', sans-serif;
font-weight: 400;
font-size: 0.95rem;
letter-spacing: 0.08em;
color: #d4b87a;
margin: 0 0 1.2rem;
padding: 0;
}
.footer-contact a {
color: #f0e6d0;
text-decoration: none;
border-bottom: 1px solid rgba(240, 230, 208, 0.4);
transition: color 200ms, border-color 200ms;
}
.footer-contact a:hover {
color: #c8860a;
border-color: rgba(200, 134, 10, 0.8);
}
.footer-links { .footer-links {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
@@ -1041,6 +1200,15 @@
word-break: break-all; word-break: break-all;
} }
.contact-inner {
grid-template-columns: 1fr;
text-align: center;
}
.contact-copy .section-heading {
text-align: center;
}
.header-ornament { .header-ornament {
display: none; display: none;
} }
@@ -1055,7 +1223,8 @@
.section-about, .section-about,
.section-series, .section-series,
.section-guide, .section-guide,
.section-qr { .section-qr,
.section-contact {
padding: 3.5rem 0; padding: 3.5rem 0;
} }
+78 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Routes, Route } from 'react-router-dom' import { Routes, Route, useNavigate } from 'react-router-dom'
import AdminPage from './AdminPage' import AdminPage from './AdminPage'
import './App.css' import './App.css'
@@ -13,6 +13,8 @@ const YOUTUBE_URL = 'https://www.youtube.com/@blackzebraem5558'
const AMAZON_MUSIC_URL = const AMAZON_MUSIC_URL =
'https://music.amazon.com/podcasts/202322bf-db86-4e7d-9a6b-4db7cbccbccf/verse-by-verse-with-nate' 'https://music.amazon.com/podcasts/202322bf-db86-4e7d-9a6b-4db7cbccbccf/verse-by-verse-with-nate'
const FACEBOOK_URL = 'https://facebook.com/versebyversewithnate' const FACEBOOK_URL = 'https://facebook.com/versebyversewithnate'
const CONTACT_FORM_ACTION = 'https://formsubmit.co/vbvwithnate@outlook.com'
const CONTACT_FORM_NEXT = '/thanks'
function FacebookIcon() { function FacebookIcon() {
return ( return (
@@ -113,6 +115,7 @@ function LandingPage({ content }: { content: SiteContent }) {
<a href="#listen">Listen</a> <a href="#listen">Listen</a>
<a href="#about">About</a> <a href="#about">About</a>
<a href="#series">Series</a> <a href="#series">Series</a>
<a href="#contact">Contact</a>
<a <a
href={SPOTIFY_SHOW_URL} href={SPOTIFY_SHOW_URL}
target="_blank" target="_blank"
@@ -321,6 +324,39 @@ function LandingPage({ content }: { content: SiteContent }) {
</div> </div>
</section> </section>
{/* ── CONTACT ── */}
<section className="section-contact" id="contact" aria-label="Contact form">
<div className="section-inner contact-inner">
<div className="contact-copy">
<p className="eyebrow">Get in Touch</p>
<h2 className="section-heading">
<span className="ornament"></span> Contact Nate <span className="ornament"></span>
</h2>
<p>
Have a question, testimony, or topic request? Send a message and we will get back to you.
</p>
</div>
<form className="contact-form" action={CONTACT_FORM_ACTION} method="POST">
<input type="hidden" name="_subject" value="New message from Verse by Verse website" />
<input type="hidden" name="_next" value={CONTACT_FORM_NEXT} />
<input type="text" name="_honey" className="contact-honeypot" tabIndex={-1} autoComplete="off" />
<label>
Name
<input type="text" name="name" required autoComplete="name" />
</label>
<label>
Email
<input type="email" name="email" required autoComplete="email" />
</label>
<label>
Message
<textarea name="message" rows={6} required />
</label>
<button type="submit" className="btn-primary">Send Message</button>
</form>
</div>
</section>
{/* ── CUSTOM RESOURCES ── */} {/* ── CUSTOM RESOURCES ── */}
{(content.customLinks ?? []).filter(l => l.placement === 'resources').length > 0 && ( {(content.customLinks ?? []).filter(l => l.placement === 'resources').length > 0 && (
<section className="section-resources" aria-label="More resources"> <section className="section-resources" aria-label="More resources">
@@ -355,6 +391,10 @@ function LandingPage({ content }: { content: SiteContent }) {
<p className="footer-ornament"> &nbsp; &nbsp; </p> <p className="footer-ornament"> &nbsp; &nbsp; </p>
<p className="footer-title">Verse by Verse with Nate</p> <p className="footer-title">Verse by Verse with Nate</p>
<p className="footer-sub">A Journey Through Scripture</p> <p className="footer-sub">A Journey Through Scripture</p>
<p className="footer-contact">
Contact:{' '}
<a href="mailto:vbvwithnate@outlook.com">vbvwithnate@outlook.com</a>
</p>
<nav className="footer-links" aria-label="Footer links"> <nav className="footer-links" aria-label="Footer links">
<a href={SPOTIFY_SHOW_URL} target="_blank" rel="noreferrer">Spotify</a> <a href={SPOTIFY_SHOW_URL} target="_blank" rel="noreferrer">Spotify</a>
<span aria-hidden="true">·</span> <span aria-hidden="true">·</span>
@@ -378,6 +418,42 @@ function LandingPage({ content }: { content: SiteContent }) {
) )
} }
function ThankYouPage() {
const navigate = useNavigate()
const [secondsLeft, setSecondsLeft] = useState(15)
useEffect(() => {
const timeoutId = window.setTimeout(() => {
navigate('/')
}, 15000)
const intervalId = window.setInterval(() => {
setSecondsLeft(s => (s > 0 ? s - 1 : 0))
}, 1000)
return () => {
window.clearTimeout(timeoutId)
window.clearInterval(intervalId)
}
}, [navigate])
return (
<main className="thanks-page" aria-label="Thank you">
<div className="thanks-card">
<p className="eyebrow">Message Received</p>
<h1>Thank You</h1>
<p>
Your message was sent successfully. We appreciate you reaching out and will get back to you soon.
</p>
<p className="thanks-countdown">Returning to the main site in {secondsLeft} seconds...</p>
<button type="button" className="btn-primary" onClick={() => navigate('/')}>
Go Back Now
</button>
</div>
</main>
)
}
export default function App() { export default function App() {
const [content, setContent] = useState<SiteContent>(DEFAULTS) const [content, setContent] = useState<SiteContent>(DEFAULTS)
@@ -395,6 +471,7 @@ export default function App() {
return ( return (
<Routes> <Routes>
<Route path="/" element={<LandingPage content={content} />} /> <Route path="/" element={<LandingPage content={content} />} />
<Route path="/thanks" element={<ThankYouPage />} />
<Route path="/admin" element={<AdminPage content={content} onSave={setContent} />} /> <Route path="/admin" element={<AdminPage content={content} onSave={setContent} />} />
</Routes> </Routes>
) )