Contact form: split name into first/last, add ntfy notification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chris 2026-06-07 00:32:10 -04:00
parent 29e0cf2938
commit 252865f626
4 changed files with 65 additions and 16 deletions

View File

@ -47,9 +47,13 @@
function validate() {
let ok = true;
const name = form.querySelector('[name="name"]');
if (!name.value.trim()) { setErr(name, 'Please enter your name.'); ok = false; }
else clearErr(name);
const firstName = form.querySelector('[name="firstName"]');
if (!firstName.value.trim()) { setErr(firstName, 'Please enter your first name.'); ok = false; }
else clearErr(firstName);
const lastName = form.querySelector('[name="lastName"]');
if (!lastName.value.trim()) { setErr(lastName, 'Please enter your last name.'); ok = false; }
else clearErr(lastName);
const email = form.querySelector('[name="email"]');
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim())) {
@ -131,7 +135,8 @@
}
const fd = new FormData();
fd.append('name', form.querySelector('[name="name"]').value.trim());
fd.append('firstName', form.querySelector('[name="firstName"]').value.trim());
fd.append('lastName', form.querySelector('[name="lastName"]').value.trim());
fd.append('email', form.querySelector('[name="email"]').value.trim());
fd.append('phone', form.querySelector('[name="phone"]').value.trim());
fd.append('message', form.querySelector('[name="message"]').value.trim());

View File

@ -90,12 +90,25 @@
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
<div class="columns is-mobile">
<div class="column">
<div class="field">
<label class="label">Name <span class="has-text-danger">*</span></label>
<label class="label">First name <span class="has-text-danger">*</span></label>
<div class="control">
<input class="input" type="text" name="name" placeholder="Your name" required autocomplete="name">
<input class="input" type="text" name="firstName" placeholder="Jane" required autocomplete="given-name">
</div>
<p class="help is-danger" id="err-firstName" style="display:none;"></p>
</div>
</div>
<div class="column">
<div class="field">
<label class="label">Last name <span class="has-text-danger">*</span></label>
<div class="control">
<input class="input" type="text" name="lastName" placeholder="Smith" required autocomplete="family-name">
</div>
<p class="help is-danger" id="err-lastName" style="display:none;"></p>
</div>
</div>
<p class="help is-danger" id="err-name" style="display:none;"></p>
</div>
<div class="field">

View File

@ -129,12 +129,25 @@
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
<div class="columns is-mobile">
<div class="column">
<div class="field">
<label class="label">Name <span class="has-text-danger">*</span></label>
<label class="label">First name <span class="has-text-danger">*</span></label>
<div class="control">
<input class="input" type="text" name="name" placeholder="Your name" required autocomplete="name">
<input class="input" type="text" name="firstName" placeholder="Jane" required autocomplete="given-name">
</div>
<p class="help is-danger" id="err-firstName" style="display:none;"></p>
</div>
</div>
<div class="column">
<div class="field">
<label class="label">Last name <span class="has-text-danger">*</span></label>
<div class="control">
<input class="input" type="text" name="lastName" placeholder="Smith" required autocomplete="family-name">
</div>
<p class="help is-danger" id="err-lastName" style="display:none;"></p>
</div>
</div>
<p class="help is-danger" id="err-name" style="display:none;"></p>
</div>
<div class="field">

View File

@ -108,9 +108,10 @@ apiRouter.post('/contact', upload.array('photos', 3), async (req, res) => {
return res.json({ success: true });
}
const { name, email, phone, message, eventType, eventDate } = req.body;
const { firstName, lastName, email, phone, message, eventType, eventDate } = req.body;
const name = [firstName, lastName].filter(Boolean).join(' ');
if (!name || !email || !phone || !message) {
if (!firstName || !lastName || !email || !phone || !message) {
return res.status(400).json({ success: false, message: 'Please fill in all required fields.' });
}
@ -173,6 +174,23 @@ apiRouter.post('/contact', upload.array('photos', 3), async (req, res) => {
transporter.sendMail(autoReply).catch(err =>
console.error(`[${new Date().toISOString()}] Auto-reply failed:`, err)
);
if (process.env.NTFY_URL) {
const ntfyBody = [
phone,
eventDateFormatted || null,
eventType || null,
message.slice(0, 100) + (message.length > 100 ? '…' : ''),
].filter(Boolean).join(' · ');
fetch(process.env.NTFY_URL, {
method: 'POST',
headers: {
'Title': `🎈 New inquiry — ${name}`,
'Priority': 'default',
'Content-Type': 'text/plain',
},
body: ntfyBody,
}).catch(err => console.error(`[${new Date().toISOString()}] ntfy failed:`, err));
}
res.json({ success: true });
} catch (err) {
console.error(`[${new Date().toISOString()}] Contact form mail error:`, err);