From 6a84e68a0322db5dc8c3e08403cff0d69866b5a1 Mon Sep 17 00:00:00 2001 From: Zak Bearman Date: Tue, 15 Sep 2026 14:02:08 +1200 Subject: [PATCH] Show request submission progress in a focused follow-up dialog --- .../app/new-requests/NewRequestClient.tsx | 53 ++++++++++++------- .../app/new-requests/request-progress.css | 18 +++++++ scripts/review_request_language_ui.cjs | 14 ++++- 3 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 frontend/app/new-requests/request-progress.css diff --git a/frontend/app/new-requests/NewRequestClient.tsx b/frontend/app/new-requests/NewRequestClient.tsx index 5bd6c58..3527ac2 100644 --- a/frontend/app/new-requests/NewRequestClient.tsx +++ b/frontend/app/new-requests/NewRequestClient.tsx @@ -1,6 +1,8 @@ 'use client' import PageHeading from '../ui/PageHeading' +import { lockBodyScroll } from '../lib/scrollLock' +import './request-progress.css' import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/navigation' @@ -96,12 +98,6 @@ const apiError = async (response: Response, fallback: string) => { return fallback } -const formatDuration = (milliseconds?: number | null) => { - if (milliseconds == null) return null - if (milliseconds < 1000) return `${Math.round(milliseconds)} ms` - return `${(milliseconds / 1000).toFixed(1)} s` -} - export default function NewRequestClient() { const router = useRouter() const searchSectionRef = useRef(null) @@ -118,6 +114,16 @@ export default function NewRequestClient() { const [selectedSeasons, setSelectedSeasons] = useState([]) const [acceptOriginalLanguage, setAcceptOriginalLanguage] = useState(null) const [submitting, setSubmitting] = useState(false) + const [progressOpen, setProgressOpen] = useState(false) + const progressDialog = useRef(null) + useEffect(() => { + if (!progressOpen) return + const previous = document.activeElement as HTMLElement | null + progressDialog.current?.showModal() + const unlock = lockBodyScroll() + return () => { progressDialog.current?.close(); unlock(); previous?.focus() } + }, [progressOpen]) + const [operation, setOperation] = useState(null) const [error, setError] = useState(null) const [success, setSuccess] = useState(null) @@ -269,12 +275,13 @@ export default function NewRequestClient() { } const submitRequest = async () => { - if (!selected || !options) return + if (!selected || !options || submitting) return if (options.media.originalLanguage && acceptOriginalLanguage === null) { setError('Choose an audio language option before requesting.'); return } if (selected.type === 'tv' && selectedSeasons.length === 0) { setError('Select at least one season.') return } + setProgressOpen(true) setSubmitting(true) setError(null) setSuccess(null) @@ -309,7 +316,7 @@ export default function NewRequestClient() { setResults((current) => current.map((item) => item.tmdbId === selected.tmdbId && item.type === selected.type ? { ...item, requestId, statusLabel: payload?.statusLabel ?? item.statusLabel } : item)) - setSuccess(requestId ? `Request #${requestId} has been accepted by Seerr.` : 'Your request has been accepted by Seerr.') + setSuccess('Your request has been received.') } catch (caught) { setError(caught instanceof Error ? caught.message : 'The request could not be submitted.') } finally { @@ -336,6 +343,22 @@ export default function NewRequestClient() { return (
+ setProgressOpen(false)} onClose={() => setProgressOpen(false)}> +
Request progress
+
+ {submitting &&
+
+ {!submitting && } + {!submitting && } + {submitting && You can close this window. Submission will continue while you stay on this page.} +
+
    @@ -513,20 +536,10 @@ export default function NewRequestClient() { ) : null} - {operation && ( -
    -
    Remote activity{operation.status === 'running' ? 'Sending your request' : operation.status === 'complete' ? 'Request hand-off complete' : 'Request hand-off needs attention'}
    {formatDuration(operation.duration_ms) && {formatDuration(operation.duration_ms)}}
    -
    - {operation.events.map((event) => ( -