Add opt-in monthly email recaps with scheduling and delivery history
Magent CI/CD / verify (push) Canceled after 3m55s
Magent CI/CD / deploy-prod (push) Canceled after 0s
Magent CI/CD / deploy-beta (push) Canceled after 0s

This commit is contained in:
2026-09-09 22:39:22 +12:00
parent 333a799e21
commit 1979e02cde
18 changed files with 1832 additions and 7 deletions
+13 -4
View File
@@ -30,6 +30,7 @@ function ChangeLabel({ change, unit = '' }: { change: Change; unit?: string }) {
export default function MonthlyReportsPage() {
const router = useRouter()
const [month, setMonth] = useState('')
const [monthReady, setMonthReady] = useState(false)
const [months, setMonths] = useState<string[]>([])
const [data, setData] = useState<MonthlyReport | null>(null)
const [busy, setBusy] = useState(true)
@@ -39,6 +40,13 @@ export default function MonthlyReportsPage() {
const [downloadError, setDownloadError] = useState('')
const downloadController = useRef<AbortController | null>(null)
useEffect(() => {
setMonth(new URLSearchParams(window.location.search).get('month') || '')
setMonthReady(true)
}, [])
useEffect(() => {
if (monthReady) window.history.replaceState(null, '', `/insights/reports${month ? `?month=${encodeURIComponent(month)}` : ''}`)
}, [month, monthReady])
useEffect(() => () => downloadController.current?.abort(), [])
const load = useCallback(async (signal: AbortSignal) => {
setBusy(true)
@@ -48,7 +56,7 @@ export default function MonthlyReportsPage() {
try {
const query = month ? `?month=${encodeURIComponent(month)}` : ''
const response = await authFetch(`${getApiBase()}/insights/reports/monthly${query}`, { signal })
if (response.status === 401) { router.replace('/login?next=%2Finsights%2Freports'); return }
if (response.status === 401) { router.replace(`/login?next=${encodeURIComponent(`/insights/reports${query}`)}`); return }
if (response.status === 403) throw new Error('Your account cannot access viewing reports. Please contact an administrator.')
if (!response.ok) {
const result = await response.json().catch(() => ({}))
@@ -63,10 +71,11 @@ export default function MonthlyReportsPage() {
}
}, [month, router])
useEffect(() => {
if (!monthReady) return
const controller = new AbortController()
void load(controller.signal)
return () => controller.abort()
}, [load, revision])
}, [load, revision, monthReady])
const download = async () => {
if (data?.state !== 'ready' || downloading) return
@@ -77,7 +86,7 @@ export default function MonthlyReportsPage() {
setDownloadError('')
try {
const response = await authFetch(`${getApiBase()}/insights/reports/monthly.csv?month=${selected}`, { signal: controller.signal })
if (response.status === 401) { router.replace('/login?next=%2Finsights%2Freports'); return }
if (response.status === 401) { router.replace(`/login?next=${encodeURIComponent(`/insights/reports?month=${selected}`)}`); return }
if (!response.ok) throw new Error('The report could not be downloaded. Please try again.')
const blob = await response.blob()
if (controller.signal.aborted) return
@@ -116,7 +125,7 @@ export default function MonthlyReportsPage() {
</div>
{downloadError && <p className="stats-notice" role="alert">{downloadError}</p>}
{busy && <div className="stats-state" role="status"><span className="stats-state-symbol" aria-hidden="true"></span><h2>Putting your month together</h2><p>Gathering your viewing history and the previous months comparison.</p></div>}
{error && <div className="stats-state" role="alert"><h2>Report couldnt load</h2><p>{error}</p><button type="button" className="ghost-button" onClick={() => setRevision((value) => value + 1)}>Try again</button></div>}
{error && <div className="stats-state" role="alert"><h2>Report couldnt load</h2><p>{error}</p><button type="button" className="ghost-button" onClick={() => setRevision((value) => value + 1)}>Try again</button>{month && <button type="button" className="ghost-button" onClick={() => setMonth('')}>Latest complete month</button>}</div>}
{data?.state === 'not_configured' && <section className="stats-state"><h2>Your monthly story starts here</h2><p>{data.is_admin ? 'Connect Jellystat to bring your monthly viewing reports into Magent.' : 'Monthly reports will appear once your administrator connects Jellystat.'}</p>{data.is_admin && <a className="stats-action" href="/admin/jellystat">Connect Jellystat</a>}</section>}
{data?.state === 'unlinked' && <section className="stats-state"><h2>Link your viewing account</h2><p>Your report needs your Jellyfin account link. Sign in using Jellyfin, or ask your administrator to review your user identities.</p>{data.is_admin && <a className="stats-action" href="/admin/identities">Review user identities</a>}</section>}
{data && summary && changes && <>