137 lines
5.1 KiB
JavaScript
137 lines
5.1 KiB
JavaScript
import { useState } from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import PublicLayout from '../../components/PublicLayout.jsx'
|
|
import PageHeader from '../../components/PageHeader.jsx'
|
|
import { Loading, ErrorState, EmptyState } from '../../components/PageState.jsx'
|
|
import { useAsync } from '../../lib/useAsync.js'
|
|
import { monthTile } from '../../lib/format.js'
|
|
import { api } from '../../api/client.js'
|
|
import { useSite } from '../../contexts/SiteContext.jsx'
|
|
|
|
export default function Newsletter() {
|
|
const { loading, error, data } = useAsync(() => api.posts('newsletter'))
|
|
const issues = data || []
|
|
|
|
return (
|
|
<PublicLayout section="website">
|
|
<div className="shell-mid page-body">
|
|
<PageHeader
|
|
eyebrow="Long-form"
|
|
title="Monthly Newsletter"
|
|
lead="A fuller monthly summary for players who want the whole picture."
|
|
/>
|
|
|
|
<SubscribeBox />
|
|
|
|
<section style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
{loading && <Loading />}
|
|
{error && <ErrorState message="Could not load newsletter issues right now." />}
|
|
{!loading && !error && issues.length === 0 && <EmptyState>No issues published yet.</EmptyState>}
|
|
{issues.map((i) => {
|
|
const tile = monthTile(i.published_at || i.created_at)
|
|
return (
|
|
<Link
|
|
key={i.id}
|
|
to={`/site/newsletter/${i.slug || i.id}`}
|
|
className="panel"
|
|
style={{ display: 'flex', gap: 20, alignItems: 'center', padding: '22px 24px', textDecoration: 'none' }}
|
|
>
|
|
<div
|
|
className="display"
|
|
style={{
|
|
flex: 'none',
|
|
width: 64,
|
|
height: 64,
|
|
borderRadius: 8,
|
|
border: '1px solid var(--line)',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
background: 'rgba(11,22,48,0.5)',
|
|
}}
|
|
>
|
|
<span style={{ color: 'var(--accent)', fontSize: '0.66rem', letterSpacing: '0.1em' }}>{tile.mon}</span>
|
|
<span style={{ color: 'var(--head)', fontSize: '1.4rem', lineHeight: 1 }}>{tile.num}</span>
|
|
</div>
|
|
<div style={{ flex: 1 }}>
|
|
<h3 className="display" style={{ margin: '0 0 4px', fontSize: '1.2rem', color: 'var(--head)' }}>
|
|
{i.title}
|
|
</h3>
|
|
<p className="muted" style={{ margin: 0, fontSize: '0.98rem' }}>
|
|
{i.excerpt || ''}
|
|
</p>
|
|
</div>
|
|
<span className="sans" style={{ flex: 'none', color: 'var(--accent)', fontSize: '0.84rem' }}>
|
|
Read →
|
|
</span>
|
|
</Link>
|
|
)
|
|
})}
|
|
</section>
|
|
</div>
|
|
</PublicLayout>
|
|
)
|
|
}
|
|
|
|
function SubscribeBox() {
|
|
const { contactEmail } = useSite()
|
|
const [email, setEmail] = useState('')
|
|
const [status, setStatus] = useState(null) // null | 'sending' | 'done' | 'mailto' | 'error'
|
|
|
|
async function onSubmit(e) {
|
|
e.preventDefault()
|
|
if (!email) return
|
|
setStatus('sending')
|
|
try {
|
|
const res = await api.contact({ email, message: `Newsletter subscription request from ${email}` })
|
|
setStatus(res && res.fallback === 'mailto' ? 'mailto' : 'done')
|
|
} catch {
|
|
setStatus('error')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<section
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 14,
|
|
flexWrap: 'wrap',
|
|
padding: '22px 24px',
|
|
border: '1px solid var(--line)',
|
|
borderRadius: 10,
|
|
background: 'rgba(19,36,60,0.4)',
|
|
marginBottom: 34,
|
|
}}
|
|
>
|
|
<span style={{ color: '#dbe2ea', fontSize: '1.02rem', flex: 1, minWidth: 220 }}>
|
|
Get each issue in your inbox the day it ships.
|
|
</span>
|
|
{status === 'done' && <span className="muted sans" style={{ fontSize: '0.9rem' }}>Thanks — we'll be in touch.</span>}
|
|
{status === 'mailto' && (
|
|
<a className="link-accent sans" href={`mailto:${contactEmail}?subject=Newsletter%20subscribe`}>
|
|
Email us to subscribe →
|
|
</a>
|
|
)}
|
|
{status !== 'done' && status !== 'mailto' && (
|
|
<form onSubmit={onSubmit} style={{ display: 'flex', gap: 10, flex: 'none', flexWrap: 'wrap' }}>
|
|
<input
|
|
type="email"
|
|
required
|
|
placeholder="you@example.com"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
className="input"
|
|
style={{ borderRadius: 999, minWidth: 200, width: 'auto' }}
|
|
/>
|
|
<button type="submit" className="btn btn-primary btn-sq" style={{ borderRadius: 999 }} disabled={status === 'sending'}>
|
|
{status === 'sending' ? 'Sending…' : 'Subscribe'}
|
|
</button>
|
|
</form>
|
|
)}
|
|
{status === 'error' && <span className="sans" style={{ color: '#d98b84', fontSize: '0.85rem' }}>Something went wrong.</span>}
|
|
</section>
|
|
)
|
|
}
|