Add contact form and thank-you redirect flow
This commit is contained in:
+170
-1
@@ -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
@@ -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">✦ ✦ ✦</p>
|
<p className="footer-ornament">✦ ✦ ✦</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>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user