Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 71 additions & 13 deletions src/pages/subscribe.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,21 @@ const meta = {
title: "Subscribe",
};

const LISTMONK_URL = "https://mailing.tylerrouze.com";
const LIST_ID = "e769d1fd-a069-436a-acf3-c523b1745b6b";
---

<PageLayout meta={meta}>
<section class="subscribe" aria-labelledby="subscribe-label">
<div class="section-label" id="subscribe-label">Subscribe</div>
<div class="content">
<p>Want to stay in touch? Subscribe below for future updates on data engineering, the modern data stack, and whatever else I happen to be thinking about.</p>
<form method="post" action={`${LISTMONK_URL}/subscription/form`} class="subscribe-form">
<form class="subscribe-form" novalidate>
<input
type="email"
name="email"
placeholder="Your email address"
required
autocomplete="email"
/>
<input type="hidden" name="l" value={LIST_ID} />
<input
type="text"
name="website"
Expand All @@ -32,13 +29,14 @@ const LIST_ID = "e769d1fd-a069-436a-acf3-c523b1745b6b";
aria-hidden="true"
style="position:absolute;left:-9999px;opacity:0;height:0;width:0;"
/>
<button type="submit">Subscribe</button>
<button type="submit" id="subscribe-btn" disabled>Subscribe</button>
<altcha-widget
id="altcha"
challengeurl="https://mailing.tylerrouze.com/api/public/captcha/altcha"
hidefooter
></altcha-widget>
</form>
<div id="subscribe-result" aria-live="polite" hidden></div>
<p class="alt-contact">
Prefer a more direct line? Send me an email at <a href="mailto:hello@tylerrouze.com">hello@tylerrouze.com</a>.
</p>
Expand All @@ -48,26 +46,83 @@ const LIST_ID = "e769d1fd-a069-436a-acf3-c523b1745b6b";

<script is:inline type="module" src="https://cdn.jsdelivr.net/npm/altcha/dist/altcha.min.js"></script>
<script is:inline>
const LISTMONK_URL = 'https://mailing.tylerrouze.com';
const LIST_UUID = 'e769d1fd-a069-436a-acf3-c523b1745b6b';

const form = document.querySelector('.subscribe-form');
const email = form.querySelector('input[type="email"]');
const emailInput = form.querySelector('input[type="email"]');
const honeypot = form.querySelector('input[name="website"]');
const widget = document.getElementById('altcha');
const btn = document.getElementById('subscribe-btn');
const result = document.getElementById('subscribe-result');

email.addEventListener('input', () => {
// Show the widget as soon as the user starts typing an email address.
emailInput.addEventListener('input', () => {
if (!widget.classList.contains('visible')) {
widget.classList.add('visible');
widget.setAttribute('auto', 'onload');
}
}, { once: true });

form.addEventListener('submit', (e) => {
// altcha injects a hidden input named "altcha" with the solution payload when solved
const payload = form.querySelector('input[name="altcha"]');
if (!payload || !payload.value) {
e.preventDefault();
// Enable the submit button only after altcha has been solved.
// This closes the window where a bot could submit before the captcha fires.
widget.addEventListener('altcha-verified', () => {
btn.disabled = false;
});

form.addEventListener('submit', async (e) => {
e.preventDefault();

// Honeypot: if filled by a bot, pretend success and stop.
if (honeypot.value) {
showResult('success');
return;
}

const altchaPayload = form.querySelector('input[name="altcha"]');
if (!altchaPayload?.value) {
widget.classList.add('visible');
widget.setAttribute('auto', 'onload');
return;
}

btn.disabled = true;
btn.textContent = 'Subscribing…';

try {
const res = await fetch(`${LISTMONK_URL}/api/public/subscription`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: emailInput.value,
list_uuids: [LIST_UUID],
captcha_solution: altchaPayload.value,
website: '',
}),
});

if (res.ok) {
showResult('success');
} else {
showResult('error');
btn.disabled = false;
btn.textContent = 'Subscribe';
}
} catch {
showResult('error');
btn.disabled = false;
btn.textContent = 'Subscribe';
}
});

function showResult(type) {
form.hidden = true;
result.hidden = false;
if (type === 'success') {
result.innerHTML = '<p>You\'re subscribed — thanks! Check your inbox to confirm.</p>';
} else {
result.innerHTML = '<p>Something went wrong. Try again or email <a href="mailto:hello@tylerrouze.com">hello@tylerrouze.com</a> directly.</p>';
}
}
</script>

<style>
Expand Down Expand Up @@ -127,6 +182,9 @@ const LIST_ID = "e769d1fd-a069-436a-acf3-c523b1745b6b";
.subscribe-form altcha-widget.visible {
display: block;
}
#subscribe-result p {
margin: 0 0 28px;
}
.alt-contact {
font-size: 15px;
color: hsl(var(--theme-text-muted));
Expand Down