mirror of
https://github.com/apophenia-news/apophenia.news.git
synced 2026-10-19 18:34:51 +00:00
Feat: Add Cloudflare Turnstile to newsletter signup
Renders an interaction-only Turnstile widget on /newsletter/ and sends its token to newsletter.planetrenox.com/api/sub, which verifies it server side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ export const PUBLIC = path.join(ROOT, "public");
|
|||||||
export const UNLISTED_PATH = path.join(ROOT, "unlisted.txt");
|
export const UNLISTED_PATH = path.join(ROOT, "unlisted.txt");
|
||||||
export const ARTICLES_GLOB = "articles/**/index.md";
|
export const ARTICLES_GLOB = "articles/**/index.md";
|
||||||
export const SITE_URL = (process.env.SITE_URL || "https://apophenia.news").replace(/\/+$/, "");
|
export const SITE_URL = (process.env.SITE_URL || "https://apophenia.news").replace(/\/+$/, "");
|
||||||
|
// Public site key of the "newsletter" Turnstile widget (secret lives on newsletter.planetrenox.com)
|
||||||
|
export const TURNSTILE_SITE_KEY = "0x4AAAAAAFG4HWETOcDDQ5vg";
|
||||||
|
|
||||||
export const AUTHOR_PAGES = [
|
export const AUTHOR_PAGES = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SITE_URL } from "./constants.mjs";
|
import { SITE_URL, TURNSTILE_SITE_KEY } from "./constants.mjs";
|
||||||
import { footer, merch, nav, shellHead } from "./templates.mjs";
|
import { footer, merch, nav, shellHead } from "./templates.mjs";
|
||||||
import { escapeHtml, escapeXml, fmtDate, renderAuthorInline, toISODate } from "./utils.mjs";
|
import { escapeHtml, escapeXml, fmtDate, renderAuthorInline, toISODate } from "./utils.mjs";
|
||||||
|
|
||||||
@@ -123,22 +123,27 @@ ${nav}
|
|||||||
this.error = '';
|
this.error = '';
|
||||||
this.ok = false;
|
this.ok = false;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
const token = () => this.$el.querySelector('[name=cf-turnstile-response]')?.value;
|
||||||
|
for (let i = 0; i < 50 && !token(); i++) await new Promise((r) => setTimeout(r, 100));
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://newsletter.planetrenox.com/api/sub', {
|
const res = await fetch('https://newsletter.planetrenox.com/api/sub', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ pool: 'apophenia', email: this.email })
|
body: JSON.stringify({ pool: 'apophenia', email: this.email, token: token() || '' })
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok && data.ok) {
|
if (res.ok && data.ok) {
|
||||||
this.ok = true;
|
this.ok = true;
|
||||||
this.email = '';
|
this.email = '';
|
||||||
} else {
|
} else {
|
||||||
this.error = data.error === 'invalid email' ? 'That email doesn\\'t look right.' : 'Something went wrong. Please try again.';
|
this.error = data.error === 'invalid email' ? 'That email doesn\\'t look right.'
|
||||||
|
: data.error === 'captcha failed' ? 'Couldn\\'t verify you\\'re human. Please try again.'
|
||||||
|
: 'Something went wrong. Please try again.';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error = 'Network error. Please try again.';
|
this.error = 'Network error. Please try again.';
|
||||||
}
|
}
|
||||||
|
window.turnstile?.reset();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -164,6 +169,7 @@ ${nav}
|
|||||||
<i data-lucide="arrow-right" class="h-4 w-4"></i>
|
<i data-lucide="arrow-right" class="h-4 w-4"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cf-turnstile mt-3" data-sitekey="${TURNSTILE_SITE_KEY}" data-appearance="interaction-only" data-size="flexible"></div>
|
||||||
|
|
||||||
<p x-show="ok" x-cloak class="mt-4 inline-flex items-center gap-2 text-sm font-medium text-emerald-700">
|
<p x-show="ok" x-cloak class="mt-4 inline-flex items-center gap-2 text-sm font-medium text-emerald-700">
|
||||||
<i data-lucide="check-circle" class="h-4 w-4"></i>
|
<i data-lucide="check-circle" class="h-4 w-4"></i>
|
||||||
@@ -176,6 +182,7 @@ ${nav}
|
|||||||
|
|
||||||
<p class="mt-4 text-xs text-zinc-500">Unsubscribe anytime. We never share your email.</p>
|
<p class="mt-4 text-xs text-zinc-500">Unsubscribe anytime. We never share your email.</p>
|
||||||
</form>
|
</form>
|
||||||
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
${merch}
|
${merch}
|
||||||
|
|||||||
Reference in New Issue
Block a user