From a055f4a8f02177976f44a5f545c90feef7a0fcb3 Mon Sep 17 00:00:00 2001 From: yusufipk Date: Tue, 18 Aug 2026 12:13:09 +0300 Subject: [PATCH] fix(upload): give the Add Video page the upgrade link too The trial ceiling refusal is drawn twice: the drag-and-drop uploader toasts it, and the Add Video page writes it into the form as submitError. Only the first one was routed through the error code, so the page that most uploads go through printed "Upgrade to get 200 GB" with nothing to click. submitError now carries whether the failure was the trial ceiling, set in the same call as the message so the link cannot outlive it, and the three places that set it hand over the failure they caught. --- .../videos/new/new-video-page-client.tsx | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/app/(dashboard)/projects/[projectId]/videos/new/new-video-page-client.tsx b/app/(dashboard)/projects/[projectId]/videos/new/new-video-page-client.tsx index 3db55f9..b14aca7 100644 --- a/app/(dashboard)/projects/[projectId]/videos/new/new-video-page-client.tsx +++ b/app/(dashboard)/projects/[projectId]/videos/new/new-video-page-client.tsx @@ -27,6 +27,7 @@ import { type VideoSource, } from '@/lib/video-providers'; import { resolvePublicBunnyCdnHostname } from '@/lib/bunny-cdn'; +import { isTrialStorageError } from '@/lib/client/api-error'; import { cleanupPendingProjectUpload, getDefaultTitleFromFile, @@ -68,7 +69,20 @@ export default function NewVideoPageClient({ const fileDragDepthRef = useRef(0); const fileInputRef = useRef(null); - const [submitError, setSubmitError] = useState(''); + const [submitError, setSubmitErrorText] = useState(''); + const [submitErrorIsTrialLimit, setSubmitErrorIsTrialLimit] = useState(false); + + /** + * The message and whether it is the trial ceiling, set together. + * + * The second half is what draws the upgrade link, so it must not outlive the + * error it belongs to. Every caller goes through here and hands over the + * failure it caught rather than keeping a flag of its own. + */ + const setSubmitError = useCallback((message: string, source?: unknown) => { + setSubmitErrorText(message); + setSubmitErrorIsTrialLimit(Boolean(message) && isTrialStorageError(source)); + }, []); const [formData, setFormData] = useState({ title: '', description: '', @@ -226,7 +240,7 @@ export default function NewVideoPageClient({ })); } }, - [formData.title] + [formData.title, setSubmitError] ); const handleFileChange = (e: React.ChangeEvent) => { @@ -362,7 +376,7 @@ export default function NewVideoPageClient({ activeTusUploadRef.current = null; failCount += 1; const message = error instanceof Error ? error.message : 'Upload failed'; - setSubmitError(`${file.name}: ${message}`); + setSubmitError(`${file.name}: ${message}`, error); setUploadStatus(''); } } @@ -420,7 +434,7 @@ export default function NewVideoPageClient({ if (!response.ok) { const data = await response.json(); - setSubmitError(data.error || 'Failed to add video'); + setSubmitError(data.error || 'Failed to add video', data); return; } @@ -447,7 +461,10 @@ export default function NewVideoPageClient({ await uploadMultipleFiles(selectedFiles); } catch (error: unknown) { console.error('Failed to add video:', error); - setSubmitError(error instanceof Error ? error.message : 'An unexpected error occurred'); + setSubmitError( + error instanceof Error ? error.message : 'An unexpected error occurred', + error + ); // Cleared on the failure path too. Leaving it set showed the error above a stale // "Initializing upload...", so the form claimed to be doing both at once. setUploadStatus(''); @@ -705,9 +722,19 @@ export default function NewVideoPageClient({ ) : null} {submitError && ( -

- - {submitError} +

+ + + {submitError} + {submitErrorIsTrialLimit && ( + + Upgrade + + )} +

)}