Add opt-in monthly email recaps with scheduling and delivery history
This commit is contained in:
@@ -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 month’s comparison.</p></div>}
|
||||
{error && <div className="stats-state" role="alert"><h2>Report couldn’t 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 couldn’t 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 && <>
|
||||
|
||||
Reference in New Issue
Block a user