feat: ship blog platform admin and deploy stack
This commit is contained in:
145
frontend/src/components/SubscriptionSignup.astro
Normal file
145
frontend/src/components/SubscriptionSignup.astro
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
import { resolvePublicApiBaseUrl } from '../lib/api/client';
|
||||
|
||||
interface Props {
|
||||
requestUrl?: string | URL;
|
||||
}
|
||||
|
||||
const { requestUrl } = Astro.props as Props;
|
||||
const subscribeApiUrl = `${resolvePublicApiBaseUrl(requestUrl)}/subscriptions`;
|
||||
---
|
||||
|
||||
<section class="terminal-subscribe-card" data-subscribe-root data-api-url={subscribeApiUrl}>
|
||||
<div class="terminal-subscribe-head">
|
||||
<p class="terminal-subscribe-kicker">newsletter / notifications</p>
|
||||
<h3>订阅更新</h3>
|
||||
<p>输入邮箱后,可以收到新文章通知;提交后需要先去邮箱点击确认链接才会正式生效。</p>
|
||||
</div>
|
||||
|
||||
<form class="terminal-subscribe-form" data-subscribe-form>
|
||||
<input type="text" name="displayName" placeholder="称呼(可选)" autocomplete="name" />
|
||||
<input type="email" name="email" placeholder="name@example.com" autocomplete="email" required />
|
||||
<button type="submit">订阅</button>
|
||||
</form>
|
||||
|
||||
<p class="terminal-subscribe-status" data-subscribe-status>支持确认订阅、退订链接和偏好管理页。</p>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('[data-subscribe-root]').forEach((root) => {
|
||||
const form = root.querySelector('[data-subscribe-form]');
|
||||
const status = root.querySelector('[data-subscribe-status]');
|
||||
const apiUrl = root.getAttribute('data-api-url');
|
||||
|
||||
if (!(form instanceof HTMLFormElement) || !(status instanceof HTMLElement) || !apiUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
const email = String(formData.get('email') || '').trim();
|
||||
const displayName = String(formData.get('displayName') || '').trim();
|
||||
|
||||
if (!email) {
|
||||
status.textContent = '请输入邮箱地址。';
|
||||
return;
|
||||
}
|
||||
|
||||
status.textContent = '提交中...';
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
displayName,
|
||||
source: 'frontend-home',
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(payload?.message || payload?.description || '订阅失败,请稍后再试。');
|
||||
}
|
||||
|
||||
form.reset();
|
||||
status.textContent =
|
||||
payload?.message || '订阅申请已提交,请前往邮箱确认后生效。';
|
||||
} catch (error) {
|
||||
status.textContent = error instanceof Error ? error.message : '订阅失败,请稍后重试。';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.terminal-subscribe-card {
|
||||
margin-top: 1.5rem;
|
||||
border: 1px solid rgba(94, 234, 212, 0.16);
|
||||
background: linear-gradient(135deg, rgba(15, 23, 42, 0.86), rgba(15, 23, 42, 0.72));
|
||||
border-radius: 1rem;
|
||||
padding: 1.1rem;
|
||||
}
|
||||
|
||||
.terminal-subscribe-kicker {
|
||||
margin: 0 0 0.35rem;
|
||||
color: var(--primary);
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.22em;
|
||||
}
|
||||
|
||||
.terminal-subscribe-head h3 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.terminal-subscribe-head p:last-child {
|
||||
margin: 0.45rem 0 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.terminal-subscribe-form {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.terminal-subscribe-form input {
|
||||
width: 100%;
|
||||
border-radius: 0.8rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
background: rgba(15, 23, 42, 0.45);
|
||||
color: var(--text-primary);
|
||||
padding: 0.85rem 0.95rem;
|
||||
}
|
||||
|
||||
.terminal-subscribe-form button {
|
||||
border: 0;
|
||||
border-radius: 0.8rem;
|
||||
padding: 0.9rem 1rem;
|
||||
font-weight: 600;
|
||||
color: #08111f;
|
||||
background: linear-gradient(135deg, var(--primary), #8b5cf6);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.terminal-subscribe-status {
|
||||
margin: 0.75rem 0 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.terminal-subscribe-form {
|
||||
grid-template-columns: minmax(180px, 0.8fr) minmax(220px, 1.2fr) auto;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user