Frontend update
This commit is contained in:
136
client/src/routes/public/Newsletter.jsx
Normal file
136
client/src/routes/public/Newsletter.jsx
Normal file
@@ -0,0 +1,136 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user