Frontend update

This commit is contained in:
2026-06-26 21:51:27 -05:00
parent eef79e2403
commit 6dba6a017c
48 changed files with 5004 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import { Link, useParams } from 'react-router-dom'
import PublicLayout from '../../components/PublicLayout.jsx'
import { Loading, ErrorState } from '../../components/PageState.jsx'
import { useAsync } from '../../lib/useAsync.js'
import { longDate, monthTile } from '../../lib/format.js'
import { api } from '../../api/client.js'
export default function NewsletterIssue() {
const { id } = useParams()
const { loading, error, data: issue } = useAsync(() => api.post('newsletter', id), [id])
return (
<PublicLayout section="website">
<div className="shell-narrow page-body">
{loading && <Loading />}
{error && (
<ErrorState
message={error.status === 404 ? 'That newsletter issue could not be found.' : 'Could not load this issue.'}
/>
)}
{issue && <Issue issue={issue} />}
{(error || issue) && (
<p style={{ marginTop: 34 }}>
<Link to="/site/newsletter" className="pill">
All issues
</Link>
</p>
)}
</div>
</PublicLayout>
)
}
function Issue({ issue }) {
const tile = monthTile(issue.published_at || issue.created_at)
const label = `${tile.mon} ${tile.num}`.trim()
return (
<article>
<p className="sans" style={{ margin: '0 0 14px', display: 'flex', gap: 8, color: 'var(--dim)', fontSize: '0.82rem' }}>
<Link to="/site/newsletter" style={{ color: 'var(--accent)', textDecoration: 'none' }}>
Newsletter
</Link>
<span>/</span>
<span>{longDate(issue.published_at || issue.created_at)}</span>
</p>
<p className="eyebrow" style={{ letterSpacing: '0.16em' }}>
Issue {label}
</p>
<h1 className="display" style={{ margin: 0, fontSize: 'clamp(2.2rem,5vw,3.2rem)', lineHeight: 1.05, color: 'var(--head)' }}>
{issue.title}
</h1>
{issue.excerpt && <p style={{ margin: '14px 0 0', color: 'var(--muted)', fontSize: '1.1rem' }}>{issue.excerpt}</p>}
<div style={{ height: 1, background: 'var(--line)', margin: '28px 0' }} />
{issue.body ? (
<div className="prose" dangerouslySetInnerHTML={{ __html: issue.body }} />
) : (
<p className="muted">This issue has no content yet.</p>
)}
</article>
)
}