feat: route ready-title issues through guided workflow
Magent CI/CD / verify (push) Successful in 1m52s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 53s

This commit is contained in:
2026-09-15 15:46:09 +12:00
parent 4ba1a5763e
commit 3465343a69
5 changed files with 116 additions and 175 deletions
@@ -1,119 +0,0 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import { authFetch, getApiBase } from '../../lib/auth'
import { lockBodyScroll } from '../../lib/scrollLock'
type Props = {
requestId: string
title: string
year?: number
requestType: string
}
const readError = async (response: Response) => {
try {
const payload = await response.json()
if (typeof payload?.detail === 'string' && payload.detail.trim()) return payload.detail
} catch {
// Use the friendly fallback below.
}
return 'Your report could not be submitted. Please try again.'
}
export default function RequestIssueDialog({ requestId, title, year, requestType }: Props) {
const dialog = useRef<HTMLDialogElement>(null)
const detailsInput = useRef<HTMLTextAreaElement>(null)
const [open, setOpen] = useState(false)
const [details, setDetails] = useState('')
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
const [issueId, setIssueId] = useState<number | null>(null)
useEffect(() => {
if (!open) return
const previous = document.activeElement as HTMLElement | null
dialog.current?.showModal()
const unlock = lockBodyScroll()
window.setTimeout(() => detailsInput.current?.focus(), 0)
return () => {
dialog.current?.close()
unlock()
previous?.focus()
}
}, [open])
const close = () => {
if (submitting) return
setOpen(false)
setError(null)
if (issueId) {
setIssueId(null)
setDetails('')
}
}
const submit = async (event: React.FormEvent) => {
event.preventDefault()
const description = details.trim()
if (!description) {
setError('Tell us what is wrong so we know what to check.')
detailsInput.current?.focus()
return
}
setSubmitting(true)
setError(null)
try {
const response = await authFetch(`${getApiBase()}/portal/items`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
kind: 'issue',
title: `Problem with ${title}`,
description,
media_type: requestType === 'tv' ? 'tv' : 'movie',
year: year ?? null,
external_ref: `/requests/${requestId}`,
issue_type: 'general',
priority: 'normal',
}),
})
if (!response.ok) throw new Error(await readError(response))
const payload = await response.json()
const createdId = Number(payload?.item?.id)
if (!Number.isInteger(createdId) || createdId <= 0) throw new Error('The issue was created, but its reference could not be loaded.')
setIssueId(createdId)
} catch (caught) {
console.error(caught)
setError(caught instanceof Error ? caught.message : 'Your report could not be submitted. Please try again.')
} finally {
setSubmitting(false)
}
}
return <>
<button type="button" className="request-problem-button" onClick={() => setOpen(true)}>Tell us what&apos;s wrong</button>
<dialog ref={dialog} className="request-issue-dialog" aria-labelledby="request-issue-title" onCancel={(event) => { event.preventDefault(); close() }}>
{issueId ? (
<div className="request-issue-success" role="status">
<span aria-hidden="true"></span>
<h2 id="request-issue-title">Thanks, we&apos;ve got it</h2>
<p>Your report is now issue #{issueId}. You can follow its progress from the Issues page.</p>
<div><a href={`/portal/issues?item=${issueId}`}>View reported issue</a><button type="button" className="ghost-button" onClick={close}>Done</button></div>
</div>
) : (
<form onSubmit={submit}>
<header>
<div><span className="section-kicker">Report a problem</span><h2 id="request-issue-title">What&apos;s wrong?</h2></div>
<button type="button" className="ghost-button" onClick={close} disabled={submitting}>Close</button>
</header>
<div className="request-issue-media"><span>{requestType === 'tv' ? 'TV show' : 'Movie'}</span><strong>{title}{year ? ` (${year})` : ''}</strong><small>Request #{requestId} · Ready to watch</small></div>
<label htmlFor="request-issue-details">Tell us what happened<textarea ref={detailsInput} id="request-issue-details" rows={6} maxLength={10000} value={details} onChange={(event) => setDetails(event.target.value)} placeholder="For example: it wont play, the audio is wrong, or the wrong version is showing…" disabled={submitting} /></label>
<p className="request-issue-help">The title and request details are included automatically.</p>
{error && <p className="error-banner" role="alert">{error}</p>}
<footer><button type="submit" disabled={submitting || !details.trim()}>{submitting ? 'Sending report…' : 'Send report'}</button></footer>
</form>
)}
</dialog>
</>
}
+8 -2
View File
@@ -4,7 +4,6 @@ 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'
@@ -542,6 +541,13 @@ export default function RequestTimelinePage() {
? availableStage.link
: null
const requestComplete = ['COMPLETED', 'AVAILABLE'].includes(snapshot.state) || availableStage?.state === 'complete'
const issueReportParams = new URLSearchParams({
reportRequest: snapshot.request_id,
title: snapshot.title,
type: snapshot.request_type === 'tv' ? 'tv' : 'movie',
})
if (snapshot.year) issueReportParams.set('year', String(snapshot.year))
const issueReportLink = `/portal/issues?${issueReportParams.toString()}`
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
@@ -833,7 +839,7 @@ export default function RequestTimelinePage() {
<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>}
{canReportIssues ? <a className="request-problem-button" href={issueReportLink}>Start issue report <span aria-hidden="true">&rarr;</span></a> : <span className="request-ready-unavailable">Issue reporting is not enabled for your account.</span>}
</section>
</div>
) : <>