Add explicit contact opt-in before Resend list signup
This commit is contained in:
@@ -85,7 +85,7 @@ function contactRateLimit(req, res, next) {
|
|||||||
|
|
||||||
app.post('/api/contact', contactRateLimit, async (req, res) => {
|
app.post('/api/contact', contactRateLimit, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { name, email, message, _honey } = req.body ?? {}
|
const { name, email, message, subscribe, _honey } = req.body ?? {}
|
||||||
|
|
||||||
// Honeypot — silently discard if filled by a bot
|
// Honeypot — silently discard if filled by a bot
|
||||||
if (_honey) {
|
if (_honey) {
|
||||||
@@ -121,6 +121,8 @@ app.post('/api/contact', contactRateLimit, async (req, res) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const resend = new Resend(process.env.RESEND_API_KEY)
|
const resend = new Resend(process.env.RESEND_API_KEY)
|
||||||
|
|
||||||
|
if (subscribe === true) {
|
||||||
const { firstName, lastName } = splitName(trimmedName)
|
const { firstName, lastName } = splitName(trimmedName)
|
||||||
const contactResend = new Resend(process.env.RESEND_CONTACTS_API_KEY ?? process.env.RESEND_API_KEY)
|
const contactResend = new Resend(process.env.RESEND_CONTACTS_API_KEY ?? process.env.RESEND_API_KEY)
|
||||||
|
|
||||||
@@ -150,6 +152,7 @@ app.post('/api/contact', contactRateLimit, async (req, res) => {
|
|||||||
} catch (contactSyncErr) {
|
} catch (contactSyncErr) {
|
||||||
console.error('[contact] contact sync exception:', contactSyncErr)
|
console.error('[contact] contact sync exception:', contactSyncErr)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { error } = await resend.emails.send({
|
const { error } = await resend.emails.send({
|
||||||
from: process.env.RESEND_FROM ?? 'Verse by Verse with Nate <onboarding@resend.dev>',
|
from: process.env.RESEND_FROM ?? 'Verse by Verse with Nate <onboarding@resend.dev>',
|
||||||
|
|||||||
+27
@@ -566,6 +566,33 @@
|
|||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-consent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.75rem;
|
||||||
|
font-family: 'Barlow Condensed', sans-serif;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-transform: none;
|
||||||
|
color: #d8c79e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-consent input {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
padding: 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
accent-color: #c8860a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-consent span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.contact-form .btn-primary {
|
.contact-form .btn-primary {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
+10
-1
@@ -105,6 +105,7 @@ function AmazonMusicIcon() {
|
|||||||
function ContactForm() {
|
function ContactForm() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [fields, setFields] = useState({ name: '', email: '', message: '' })
|
const [fields, setFields] = useState({ name: '', email: '', message: '' })
|
||||||
|
const [subscribe, setSubscribe] = useState(false)
|
||||||
const [honey, setHoney] = useState('')
|
const [honey, setHoney] = useState('')
|
||||||
const [status, setStatus] = useState<'idle' | 'submitting' | 'error'>('idle')
|
const [status, setStatus] = useState<'idle' | 'submitting' | 'error'>('idle')
|
||||||
const [errorMsg, setErrorMsg] = useState('')
|
const [errorMsg, setErrorMsg] = useState('')
|
||||||
@@ -121,7 +122,7 @@ function ContactForm() {
|
|||||||
const res = await fetch('/api/contact', {
|
const res = await fetch('/api/contact', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ ...fields, _honey: honey }),
|
body: JSON.stringify({ ...fields, subscribe, _honey: honey }),
|
||||||
})
|
})
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json().catch(() => ({}))
|
const data = await res.json().catch(() => ({}))
|
||||||
@@ -159,6 +160,14 @@ function ContactForm() {
|
|||||||
Message
|
Message
|
||||||
<textarea name="message" rows={6} required value={fields.message} onChange={handleChange} />
|
<textarea name="message" rows={6} required value={fields.message} onChange={handleChange} />
|
||||||
</label>
|
</label>
|
||||||
|
<label className="contact-consent">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={subscribe}
|
||||||
|
onChange={e => setSubscribe(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span>Send me updates from Verse by Verse with Nate. I can unsubscribe anytime.</span>
|
||||||
|
</label>
|
||||||
{status === 'error' && <p className="contact-error">{errorMsg}</p>}
|
{status === 'error' && <p className="contact-error">{errorMsg}</p>}
|
||||||
<button type="submit" className="btn-primary" disabled={status === 'submitting'}>
|
<button type="submit" className="btn-primary" disabled={status === 'submitting'}>
|
||||||
{status === 'submitting' ? 'Sending…' : 'Send Message'}
|
{status === 'submitting' ? 'Sending…' : 'Send Message'}
|
||||||
|
|||||||
Reference in New Issue
Block a user