Add Grizzlyflix newsletters with curated editions and weekly delivery
Magent CI/CD / verify (push) Successful in 10m54s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 1m14s

This commit is contained in:
2026-09-09 23:42:52 +12:00
parent e014baadc3
commit b0f8c89db7
22 changed files with 2223 additions and 21 deletions
@@ -0,0 +1,70 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import { getApiBase } from '../lib/auth'
import BrandingLogo from '../ui/BrandingLogo'
import '../email-recaps/recaps.css'
type LinkAction = { action: 'confirm' | 'unsubscribe'; token: string }
export default function NewsletterLinkPage() {
const [link, setLink] = useState<LinkAction | null>(null)
const [state, setState] = useState('loading')
const [error, setError] = useState('')
const [busy, setBusy] = useState(false)
const currentLink = useRef<LinkAction | null>(null)
useEffect(() => {
let controller: AbortController | null = null
const checkLink = () => {
controller?.abort()
const abort = new AbortController()
controller = abort
setError(''); setState('loading'); setLink(null); setBusy(false); currentLink.current = null
// Fragments stay out of web-server access logs and referrers. Opening the link only checks it.
const params = new URLSearchParams(window.location.hash.slice(1))
const action = params.get('action')
const token = params.get('token') || ''
if ((action !== 'confirm' && action !== 'unsubscribe') || !/^[A-Za-z0-9_-]{40,100}$/.test(token)) {
setError('This email link is incomplete. Open Profile to manage your newsletters.'); setState('error'); return
}
const payload = { action, token } as LinkAction
currentLink.current = payload
setLink(payload)
void fetch(`${getApiBase()}/newsletter-subscription/check`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: abort.signal, credentials: 'omit' }).then(async (response) => {
const result = await response.json().catch(() => ({}))
if (!response.ok) throw new Error(typeof result.detail === 'string' ? result.detail : 'Could not check this email link. Please open it again.')
if (!abort.signal.aborted) setState(result.state)
}).catch((err: Error) => { if (!abort.signal.aborted) { setError(err.message); setState('error') } })
}
checkLink()
window.addEventListener('hashchange', checkLink)
return () => { currentLink.current = null; controller?.abort(); window.removeEventListener('hashchange', checkLink) }
}, [])
const apply = async () => {
if (!link || busy) return
const payload = link
setBusy(true); setError('')
try {
const response = await fetch(`${getApiBase()}/newsletter-subscription/confirm`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), credentials: 'omit' })
const result = await response.json().catch(() => ({}))
if (currentLink.current !== payload) return
if (!response.ok) throw new Error(typeof result.detail === 'string' ? result.detail : 'Could not update your preference. Please try again.')
setState(result.state)
window.history.replaceState(null, '', '/newsletter-subscription')
} catch (err) { if (currentLink.current === payload) setError(err instanceof Error ? err.message : 'Could not update your preference.') }
finally { if (currentLink.current === payload) setBusy(false) }
}
const done = state === 'enabled' || state === 'off'
return <main className="recap-link-page"><a className="recap-brand" href="/login"><BrandingLogo className="brand-logo" /><span>Magent</span></a><section className="account-panel">
<span className="recap-eyebrow">Grizzlyflix newsletters</span>
<h1>{state === 'enabled' ? 'Youre on the list.' : state === 'off' ? 'Newsletters are turned off.' : state === 'loading' ? 'Checking your email link' : state === 'error' ? 'This link needs another look' : link?.action === 'unsubscribe' ? 'Unsubscribe from newsletters?' : 'Your next watch starts here.'}</h1>
<p>{state === 'enabled' ? 'Your email is confirmed. Youll receive your personal viewing recap when the monthly schedule runs.' : state === 'off' ? 'You wont receive further monthly recaps. You can turn them back on in Profile.' : state === 'ready' && link?.action === 'unsubscribe' ? 'This turns off new-arrival newsletters. Your personal monthly recaps are managed separately.' : state === 'ready' ? 'Confirm to receive new movies, TV updates and featured picks, with posters and links to watch.' : ''}</p>
{error && <p className="account-notice is-error" role="alert">{error}</p>}
{state === 'ready' && <button type="button" className="account-primary" disabled={busy} onClick={() => void apply()}>{busy ? 'Updating…' : link?.action === 'unsubscribe' ? 'Unsubscribe from newsletters' : 'Confirm newsletter subscription'}</button>}
{(done || state === 'error') && <a className="recap-text-link" href="/profile#newsletters">Manage email preferences </a>}
{state === 'loading' && <p role="status">One moment</p>}
</section></main>
}