mirror of
https://github.com/apophenia-news/apophenia.news.git
synced 2026-10-19 10:24:52 +00:00
Add newsletter unsubscribe page
This commit is contained in:
@@ -6,7 +6,7 @@ import { marked } from "marked";
|
|||||||
import markedKatex from "marked-katex-extension";
|
import markedKatex from "marked-katex-extension";
|
||||||
import { ARTICLES_GLOB, AUTHOR_PAGES, ROOT, SRC, UNLISTED_PATH } from "./lib/constants.mjs";
|
import { ARTICLES_GLOB, AUTHOR_PAGES, ROOT, SRC, UNLISTED_PATH } from "./lib/constants.mjs";
|
||||||
import { fixInternalLinks } from "./lib/utils.mjs";
|
import { fixInternalLinks } from "./lib/utils.mjs";
|
||||||
import { renderArticle, renderHome, renderWritePage, renderNewsletterPage, renderAuthorPage } from "./lib/renderers.mjs";
|
import { renderArticle, renderHome, renderWritePage, renderNewsletterPage, renderUnsubscribePage, renderAuthorPage } from "./lib/renderers.mjs";
|
||||||
import { ensureCleanGenerated, writeDiscoveryFiles, writePage } from "./lib/io.mjs";
|
import { ensureCleanGenerated, writeDiscoveryFiles, writePage } from "./lib/io.mjs";
|
||||||
|
|
||||||
marked.use(markedKatex({ throwOnError: false, nonStandard: true }));
|
marked.use(markedKatex({ throwOnError: false, nonStandard: true }));
|
||||||
@@ -66,6 +66,7 @@ const run = async () => {
|
|||||||
await writePage([SRC, "index.html"], renderHome(listedArticles));
|
await writePage([SRC, "index.html"], renderHome(listedArticles));
|
||||||
await writePage([SRC, "write", "index.html"], renderWritePage());
|
await writePage([SRC, "write", "index.html"], renderWritePage());
|
||||||
await writePage([SRC, "newsletter", "index.html"], renderNewsletterPage());
|
await writePage([SRC, "newsletter", "index.html"], renderNewsletterPage());
|
||||||
|
await writePage([SRC, "unsubscribe", "index.html"], renderUnsubscribePage());
|
||||||
|
|
||||||
for (const author of AUTHOR_PAGES) {
|
for (const author of AUTHOR_PAGES) {
|
||||||
await writePage([SRC, "author", author.slug, "index.html"], renderAuthorPage(author));
|
await writePage([SRC, "author", author.slug, "index.html"], renderAuthorPage(author));
|
||||||
@@ -78,7 +79,7 @@ const run = async () => {
|
|||||||
|
|
||||||
await writeDiscoveryFiles(listedArticles, AUTHOR_PAGES);
|
await writeDiscoveryFiles(listedArticles, AUTHOR_PAGES);
|
||||||
|
|
||||||
console.log(`Generated ${articles.length} article pages (${listedArticles.length} listed) + home + write + newsletter + author pages + rss/sitemap/robots.`);
|
console.log(`Generated ${articles.length} article pages (${listedArticles.length} listed) + home + write + newsletter + unsubscribe + author pages + rss/sitemap/robots.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
run().catch((err) => {
|
run().catch((err) => {
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ ${nav}
|
|||||||
<span x-text="error"></span>
|
<span x-text="error"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<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"><a href="/unsubscribe/" class="underline">Unsubscribe</a> anytime. We never share your email.</p>
|
||||||
</form>
|
</form>
|
||||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||||
</article>
|
</article>
|
||||||
@@ -196,6 +196,59 @@ ${nav}
|
|||||||
${footer}
|
${footer}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const renderUnsubscribePage = () => `
|
||||||
|
${shellHead({
|
||||||
|
title: "Unsubscribe — apophenia.news",
|
||||||
|
desc: "Unsubscribe from the apophenia.news newsletter.",
|
||||||
|
url: `${SITE_URL}/unsubscribe/`
|
||||||
|
})}
|
||||||
|
${nav}
|
||||||
|
<main class="shell py-10">
|
||||||
|
<section class="card max-w-md mx-auto p-6 sm:p-8">
|
||||||
|
<h1 class="text-3xl font-bold">Unsubscribe</h1>
|
||||||
|
<p class="mt-3 text-zinc-600">Enter your email to stop receiving our newsletter.</p>
|
||||||
|
<form class="mt-6" x-data="{
|
||||||
|
email: '', loading: false, ok: false, error: '',
|
||||||
|
async submit() {
|
||||||
|
if (this.loading) return;
|
||||||
|
this.loading = true;
|
||||||
|
this.ok = false;
|
||||||
|
this.error = '';
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://newsletter.planetrenox.com/api/unsub', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ pool: 'apophenia', email: this.email.trim() })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok && data.ok) {
|
||||||
|
this.ok = true;
|
||||||
|
this.email = '';
|
||||||
|
} else {
|
||||||
|
this.error = data.error === 'invalid email' ? 'Please enter a valid email address.' : 'Something went wrong. Please try again.';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.error = 'Could not connect. Please try again.';
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}" @submit.prevent="submit">
|
||||||
|
<label for="unsubscribe-email" class="block text-sm font-medium">Email address</label>
|
||||||
|
<input id="unsubscribe-email" name="email" type="email" autocomplete="email" required x-model="email" :disabled="loading"
|
||||||
|
placeholder="you@example.com" class="mt-2 w-full rounded-xl border border-zinc-300 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-400" />
|
||||||
|
<button type="submit" :disabled="loading"
|
||||||
|
class="mt-3 w-full rounded-xl bg-accent px-4 py-3 font-medium text-white transition hover:bg-indigo-700 disabled:opacity-60"
|
||||||
|
x-text="loading ? 'Unsubscribing…' : 'Unsubscribe'">Unsubscribe</button>
|
||||||
|
<p x-show="ok" x-cloak role="status" class="mt-4 text-sm text-emerald-700">You're unsubscribed from the Apophenia newsletter.</p>
|
||||||
|
<p x-show="error" x-cloak role="alert" x-text="error" class="mt-4 text-sm text-rose-700"></p>
|
||||||
|
</form>
|
||||||
|
<a href="/newsletter/" class="mt-6 inline-block text-sm">Back to newsletter</a>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
${footer}
|
||||||
|
`;
|
||||||
|
|
||||||
export const renderWritePage = () => `
|
export const renderWritePage = () => `
|
||||||
${shellHead({
|
${shellHead({
|
||||||
title: "Become a writer for apophenia.news",
|
title: "Become a writer for apophenia.news",
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user