Files
Magent/frontend/app/requests/[id]/RequestIssueDialog.tsx
T
Assclaw 4ba1a5763e
Magent CI/CD / verify (push) Successful in 1m56s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 48s
feat: simplify ready requests and add global search
2026-09-15 15:15:35 +12:00

120 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
</>
}