fix(invoices): stop popup blockers from silently eating the PDF preview tab (#1191)
* fix(invoices): stop popup blockers from silently eating the PDF preview tab A window.open() after an await runs outside the click's transient user activation (~5s, less in Safari), so the preview tab was popup-blocked exactly when generation was slow (cold start + logo re-encode). The request succeeded, nothing opened, no error: the button looked locked (support: carina@cbysea.se). - lib/browser/deferred-tab.ts: open the tab synchronously in the click, navigate it when the result arrives, close it on failure (the pattern AGIPanel already used for its signing tab), with unit tests. - InvoiceEditor: preview uses the deferred tab + popup-blocked toast; revoke the blob URL instead of leaking it; guard the review dialog against an unresolved customer (silent no-op click); 5s timeout on the pre-review next-number fetch; spinner + disable while the submit handler is in flight in create mode. - Same pre-open fix in TransactionAttachmentIndicator, JournalEntryAttachments (failures now toast instead of vanishing), DocumentViewButton, and the Arcim reconnect OAuth popup (its 'trusted gesture' comment was wrong after the await). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(invoices): review triage: close blocked preview tab, precise popup hint, test convention Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b5e3c476a5
commit
f07a34c51b
@@ -4,6 +4,7 @@ import { useState } from 'react'
|
||||
import { ExternalLink, Loader2 } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { openDeferredTab } from '@/lib/browser/deferred-tab'
|
||||
|
||||
interface DocumentViewButtonProps {
|
||||
documentId: string
|
||||
@@ -35,10 +36,14 @@ export function DocumentViewButton({ documentId, label = 'Visa dokument', classN
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
// Pre-open inside the click's user activation: a window.open after the
|
||||
// await is popup-blocked when the signed-URL fetch is slow.
|
||||
const tab = openDeferredTab('Laddar...')
|
||||
try {
|
||||
const res = await fetch(`/api/documents/${documentId}`)
|
||||
const json = await res.json().catch(() => ({}))
|
||||
if (!res.ok || !json?.data?.download_url) {
|
||||
tab.close()
|
||||
toast({
|
||||
title: 'Kunde inte öppna dokumentet',
|
||||
description: json?.error || 'Försök igen om en stund.',
|
||||
@@ -46,7 +51,19 @@ export function DocumentViewButton({ documentId, label = 'Visa dokument', classN
|
||||
})
|
||||
return
|
||||
}
|
||||
window.open(json.data.download_url as string, '_blank', 'noopener,noreferrer')
|
||||
if (!tab.navigate(json.data.download_url as string)) {
|
||||
tab.close()
|
||||
toast({
|
||||
title: 'Kunde inte öppna dokumentet',
|
||||
description: tab.blocked
|
||||
? 'Tillåt popupfönster för Accounted i webbläsaren och försök igen.'
|
||||
: 'Försök igen om en stund.',
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
tab.close()
|
||||
toast({ title: 'Kunde inte öppna dokumentet', variant: 'destructive' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { openDeferredTab } from '@/lib/browser/deferred-tab'
|
||||
import {
|
||||
FileText,
|
||||
ImageIcon,
|
||||
@@ -70,6 +71,7 @@ export default function JournalEntryAttachments({
|
||||
onCountChange,
|
||||
}: JournalEntryAttachmentsProps) {
|
||||
const t = useTranslations('journal_attachments')
|
||||
const tCommon = useTranslations('common')
|
||||
const { toast } = useToast()
|
||||
const [documents, setDocuments] = useState<DocumentRecord[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -154,14 +156,23 @@ export default function JournalEntryAttachments({
|
||||
}, [uploadFiles, fetchDocuments])
|
||||
|
||||
const handleDownload = async (docId: string) => {
|
||||
// Pre-open inside the click's user activation: a window.open after the
|
||||
// await is popup-blocked when the signed-URL fetch is slow.
|
||||
const tab = openDeferredTab(tCommon('loading'))
|
||||
try {
|
||||
const res = await fetch(`/api/documents/${docId}`)
|
||||
const { data } = await res.json()
|
||||
if (data?.download_url) {
|
||||
window.open(data.download_url, '_blank')
|
||||
if (!data?.download_url || !tab.navigate(data.download_url)) {
|
||||
tab.close()
|
||||
toast({
|
||||
title: t('download_failed'),
|
||||
description: tab.blocked ? tCommon('popup_blocked_description') : undefined,
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// Non-critical: silently ignore
|
||||
tab.close()
|
||||
toast({ title: t('download_failed'), variant: 'destructive' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user