Switch contact form to self-hosted SMTP via nodemailer, remove formsubmit.co
This commit is contained in:
+14
@@ -572,6 +572,20 @@
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.contact-form .btn-primary:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.contact-error {
|
||||
font-family: 'Barlow Condensed', sans-serif;
|
||||
font-size: 0.95rem;
|
||||
color: #e05c5c;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.contact-honeypot {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
|
||||
+66
-20
@@ -13,8 +13,6 @@ const YOUTUBE_URL = 'https://www.youtube.com/@blackzebraem5558'
|
||||
const AMAZON_MUSIC_URL =
|
||||
'https://music.amazon.com/podcasts/202322bf-db86-4e7d-9a6b-4db7cbccbccf/verse-by-verse-with-nate'
|
||||
const FACEBOOK_URL = 'https://facebook.com/versebyversewithnate'
|
||||
const CONTACT_FORM_ACTION = 'https://formsubmit.co/vbvwithnate@outlook.com'
|
||||
const CONTACT_FORM_NEXT = '/thanks'
|
||||
|
||||
function FacebookIcon() {
|
||||
return (
|
||||
@@ -104,6 +102,71 @@ function AmazonMusicIcon() {
|
||||
)
|
||||
}
|
||||
|
||||
function ContactForm() {
|
||||
const navigate = useNavigate()
|
||||
const [fields, setFields] = useState({ name: '', email: '', message: '' })
|
||||
const [honey, setHoney] = useState('')
|
||||
const [status, setStatus] = useState<'idle' | 'submitting' | 'error'>('idle')
|
||||
const [errorMsg, setErrorMsg] = useState('')
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
|
||||
setFields(f => ({ ...f, [e.target.name]: e.target.value }))
|
||||
}
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
setStatus('submitting')
|
||||
setErrorMsg('')
|
||||
try {
|
||||
const res = await fetch('/api/contact', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...fields, _honey: honey }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
setErrorMsg((data as { message?: string }).message ?? 'Something went wrong. Please try again.')
|
||||
setStatus('error')
|
||||
return
|
||||
}
|
||||
navigate('/thanks')
|
||||
} catch {
|
||||
setErrorMsg('Could not connect. Please try again later.')
|
||||
setStatus('error')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="contact-form" onSubmit={handleSubmit} noValidate>
|
||||
<input
|
||||
type="text"
|
||||
className="contact-honeypot"
|
||||
tabIndex={-1}
|
||||
autoComplete="off"
|
||||
aria-hidden="true"
|
||||
value={honey}
|
||||
onChange={e => setHoney(e.target.value)}
|
||||
/>
|
||||
<label>
|
||||
Name
|
||||
<input type="text" name="name" required autoComplete="name" value={fields.name} onChange={handleChange} />
|
||||
</label>
|
||||
<label>
|
||||
Email
|
||||
<input type="email" name="email" required autoComplete="email" value={fields.email} onChange={handleChange} />
|
||||
</label>
|
||||
<label>
|
||||
Message
|
||||
<textarea name="message" rows={6} required value={fields.message} onChange={handleChange} />
|
||||
</label>
|
||||
{status === 'error' && <p className="contact-error">{errorMsg}</p>}
|
||||
<button type="submit" className="btn-primary" disabled={status === 'submitting'}>
|
||||
{status === 'submitting' ? 'Sending…' : 'Send Message'}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function LandingPage({ content }: { content: SiteContent }) {
|
||||
return (
|
||||
<div className="site">
|
||||
@@ -336,24 +399,7 @@ function LandingPage({ content }: { content: SiteContent }) {
|
||||
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>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user