feat: simplify ready requests and add global search
Magent CI/CD / verify (push) Successful in 1m56s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 48s

This commit is contained in:
2026-09-15 15:15:35 +12:00
parent dd51332f3c
commit 4ba1a5763e
10 changed files with 600 additions and 49 deletions
+39 -35
View File
@@ -4,11 +4,13 @@ import PageHeading from '../../ui/PageHeading'
import RequestLanguage from './RequestLanguage'
import { lockBodyScroll } from '../../lib/scrollLock'
import LatestActivity from './LatestActivity'
import RequestIssueDialog from './RequestIssueDialog'
import Image from 'next/image'
import { useParams, useRouter } from 'next/navigation'
import { useEffect, useMemo, useState } from 'react'
import { authFetch, clearToken, getApiBase, getToken } from '../../lib/auth'
import { canAccess } from '../../lib/features'
type TimelineHop = {
service: string
@@ -325,6 +327,7 @@ export default function RequestTimelinePage() {
const [historyActions, setHistoryActions] = useState<ActionHistory[]>([])
const [operationProgress, setOperationProgress] = useState<OperationProgress | null>(null)
const [isAdmin, setIsAdmin] = useState(false)
const [canReportIssues, setCanReportIssues] = useState(false)
const awaitingMediaIndex = Boolean(
snapshot?.presentation?.pipeline?.some(
(stage) => stage.id === 'available' && stage.state === 'active'
@@ -386,6 +389,7 @@ export default function RequestTimelinePage() {
const me = await meResponse.json()
const viewerIsAdmin = me?.role === 'admin'
setIsAdmin(viewerIsAdmin)
setCanReportIssues(canAccess(me, 'issues'))
if (!snapshotResponse.ok) {
throw new Error(await readApiError(snapshotResponse, 'Unable to load this request.'))
}
@@ -537,6 +541,7 @@ export default function RequestTimelinePage() {
const mediaServerLink = availableStage?.state === 'complete' && availableStage.link
? availableStage.link
: null
const requestComplete = ['COMPLETED', 'AVAILABLE'].includes(snapshot.state) || availableStage?.state === 'complete'
const statusLabel = presentation.status?.label ?? fallbackStatusLabel(snapshot.state)
const statusMeaning = presentation.status?.meaning ?? snapshot.state_reason ?? 'Magent is checking this request.'
const download = presentation.download
@@ -815,40 +820,39 @@ export default function RequestTimelinePage() {
</div>
)}
{operationProgress && <LatestActivity operation={operationProgress} besideDownload={downloadVisible} onDismiss={() => setOperationProgress(null)} />}
<div className="request-overview-block request-next-step">
<div className="request-next-step-main">
<div className="request-next-step-copy">
<span className="request-overview-label">Next step</span>
<strong>{nextStep.title}</strong>
<p>{nextStep.description}</p>
<div className={`request-overview-block request-next-step ${requestComplete ? 'is-ready' : ''}`}>
{requestComplete ? (
<div className="request-ready-actions">
<section>
<span className="request-overview-label">Ready to watch</span>
<strong>Watch this now!</strong>
<p>Open {snapshot.title} directly in Grizzlyflix.</p>
{mediaServerLink ? <a className="request-watch-button" href={mediaServerLink} target="_blank" rel="noreferrer">Watch on Grizzlyflix <span aria-hidden="true">&rarr;</span></a> : <span className="request-ready-unavailable">The Grizzlyflix watch link is not configured.</span>}
</section>
<section>
<span className="request-overview-label">Need help?</span>
<strong>Is there a problem with this?</strong>
<p>Let us know what is wrong and we&apos;ll attach the title and request details automatically.</p>
{canReportIssues ? <RequestIssueDialog requestId={snapshot.request_id} title={snapshot.title} year={snapshot.year} requestType={snapshot.request_type} /> : <span className="request-ready-unavailable">Issue reporting is not enabled for your account.</span>}
</section>
</div>
{mediaServerLink && (
<a
className="request-watch-button"
href={mediaServerLink}
target="_blank"
rel="noreferrer"
>
Watch on Grizzlyflix <span aria-hidden="true">&rarr;</span>
</a>
)}
</div>
<div className="request-action-row">
{recommendedActions.map((action) => (
<button key={action.id} type="button" disabled={Boolean(busyAction)} onClick={() => void runAction(action)}>
{busyAction === action.id ? 'Working…' : action.label}
</button>
))}
<button
type="button"
className="request-recheck-button"
disabled={Boolean(busyAction)}
onClick={() => void recheckRequest()}
title="Recheck Seerr, the library collector, qBittorrent, and the media server"
>
{busyAction === 'recheck_pipeline' ? 'Rechecking…' : 'Recheck request'}
</button>
</div>
) : <>
<div className="request-next-step-main">
<div className="request-next-step-copy">
<span className="request-overview-label">Next step</span>
<strong>{nextStep.title}</strong>
<p>{nextStep.description}</p>
</div>
</div>
<div className="request-action-row">
{recommendedActions.map((action) => (
<button key={action.id} type="button" disabled={Boolean(busyAction)} onClick={() => void runAction(action)}>
{busyAction === action.id ? 'Working…' : action.label}
</button>
))}
<button type="button" className="request-recheck-button" disabled={Boolean(busyAction)} onClick={() => void recheckRequest()} title="Recheck Seerr, the library collector, qBittorrent, and the media server">{busyAction === 'recheck_pipeline' ? 'Rechecking…' : 'Recheck request'}</button>
</div>
</>}
</div>
{(actionMessage || actionError) && (
<div className={`request-action-feedback ${actionError ? 'is-error' : 'is-success'}`} role="status">
@@ -886,7 +890,7 @@ export default function RequestTimelinePage() {
</section>
)}
<section className="request-journey" aria-labelledby="request-journey-heading">
{!requestComplete && <section className="request-journey" aria-labelledby="request-journey-heading">
<div className="request-journey-heading">
<div>
<span className="section-kicker">Live collection path</span>
@@ -975,7 +979,7 @@ export default function RequestTimelinePage() {
})}
</div>
</section>
</section>}
{releasePickerOpen && (
<div className="request-release-modal-layer">