mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
add a dismiss all errors button and change the wording of invalid file errors
This commit is contained in:
parent
428f9eadbe
commit
245d1cb551
@ -75,6 +75,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"pdfPassword": "The PDF Document is passworded and either the password was not provided or was incorrect",
|
"pdfPassword": "The PDF Document is passworded and either the password was not provided or was incorrect",
|
||||||
"_value": "Error",
|
"_value": "Error",
|
||||||
|
"dismissAllErrors": "Dismiss All Errors",
|
||||||
"sorry": "Sorry for the issue!",
|
"sorry": "Sorry for the issue!",
|
||||||
"needHelp": "Need help / Found an issue?",
|
"needHelp": "Need help / Found an issue?",
|
||||||
"contactTip": "If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:",
|
"contactTip": "If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useCallback, useRef, useMemo } from 'react';
|
import React, { useState, useCallback, useRef, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Text, Center, Box, Notification, LoadingOverlay, Stack, Group, Portal
|
Text, Center, Box, LoadingOverlay, Stack, Group
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { useFileSelection, useFileState, useFileManagement } from '../../contexts/FileContext';
|
import { useFileSelection, useFileState, useFileManagement } from '../../contexts/FileContext';
|
||||||
@ -47,8 +47,8 @@ const FileEditor = ({
|
|||||||
// Get file selection context
|
// Get file selection context
|
||||||
const { setSelectedFiles } = useFileSelection();
|
const { setSelectedFiles } = useFileSelection();
|
||||||
|
|
||||||
const [status, setStatus] = useState<string | null>(null);
|
const [_status, _setStatus] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [_error, _setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Toast helpers
|
// Toast helpers
|
||||||
const showStatus = useCallback((message: string, type: 'neutral' | 'success' | 'warning' | 'error' = 'neutral') => {
|
const showStatus = useCallback((message: string, type: 'neutral' | 'success' | 'warning' | 'error' = 'neutral') => {
|
||||||
@ -91,7 +91,7 @@ const FileEditor = ({
|
|||||||
|
|
||||||
// Process uploaded files using context
|
// Process uploaded files using context
|
||||||
const handleFileUpload = useCallback(async (uploadedFiles: File[]) => {
|
const handleFileUpload = useCallback(async (uploadedFiles: File[]) => {
|
||||||
setError(null);
|
_setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const allExtractedFiles: File[] = [];
|
const allExtractedFiles: File[] = [];
|
||||||
@ -224,7 +224,7 @@ const FileEditor = ({
|
|||||||
|
|
||||||
// Update context (this automatically updates tool selection since they use the same action)
|
// Update context (this automatically updates tool selection since they use the same action)
|
||||||
setSelectedFiles(newSelection);
|
setSelectedFiles(newSelection);
|
||||||
}, [setSelectedFiles, toolMode, setStatus, activeStirlingFileStubs]);
|
}, [setSelectedFiles, toolMode, _setStatus, activeStirlingFileStubs]);
|
||||||
|
|
||||||
|
|
||||||
// File reordering handler for drag and drop
|
// File reordering handler for drag and drop
|
||||||
@ -281,7 +281,7 @@ const FileEditor = ({
|
|||||||
// Update status
|
// Update status
|
||||||
const moveCount = filesToMove.length;
|
const moveCount = filesToMove.length;
|
||||||
showStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`);
|
showStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`);
|
||||||
}, [activeStirlingFileStubs, reorderFiles, setStatus]);
|
}, [activeStirlingFileStubs, reorderFiles, _setStatus]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -306,7 +306,7 @@ const FileEditor = ({
|
|||||||
if (record && file) {
|
if (record && file) {
|
||||||
downloadBlob(file, file.name);
|
downloadBlob(file, file.name);
|
||||||
}
|
}
|
||||||
}, [activeStirlingFileStubs, selectors, setStatus]);
|
}, [activeStirlingFileStubs, selectors, _setStatus]);
|
||||||
|
|
||||||
const handleViewFile = useCallback((fileId: FileId) => {
|
const handleViewFile = useCallback((fileId: FileId) => {
|
||||||
const record = activeStirlingFileStubs.find(r => r.id === fileId);
|
const record = activeStirlingFileStubs.find(r => r.id === fileId);
|
||||||
@ -417,7 +417,7 @@ const FileEditor = ({
|
|||||||
onToggleFile={toggleFile}
|
onToggleFile={toggleFile}
|
||||||
onDeleteFile={handleDeleteFile}
|
onDeleteFile={handleDeleteFile}
|
||||||
onViewFile={handleViewFile}
|
onViewFile={handleViewFile}
|
||||||
onSetStatus={showStatus}
|
_onSetStatus={showStatus}
|
||||||
onReorderFiles={handleReorderFiles}
|
onReorderFiles={handleReorderFiles}
|
||||||
onDownloadFile={handleDownloadFile}
|
onDownloadFile={handleDownloadFile}
|
||||||
toolMode={toolMode}
|
toolMode={toolMode}
|
||||||
|
@ -29,7 +29,7 @@ interface FileEditorThumbnailProps {
|
|||||||
onToggleFile: (fileId: FileId) => void;
|
onToggleFile: (fileId: FileId) => void;
|
||||||
onDeleteFile: (fileId: FileId) => void;
|
onDeleteFile: (fileId: FileId) => void;
|
||||||
onViewFile: (fileId: FileId) => void;
|
onViewFile: (fileId: FileId) => void;
|
||||||
onSetStatus: (status: string) => void;
|
_onSetStatus: (status: string) => void;
|
||||||
onReorderFiles?: (sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => void;
|
onReorderFiles?: (sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => void;
|
||||||
onDownloadFile: (fileId: FileId) => void;
|
onDownloadFile: (fileId: FileId) => void;
|
||||||
toolMode?: boolean;
|
toolMode?: boolean;
|
||||||
@ -42,7 +42,7 @@ const FileEditorThumbnail = ({
|
|||||||
selectedFiles,
|
selectedFiles,
|
||||||
onToggleFile,
|
onToggleFile,
|
||||||
onDeleteFile,
|
onDeleteFile,
|
||||||
onSetStatus,
|
_onSetStatus,
|
||||||
onReorderFiles,
|
onReorderFiles,
|
||||||
onDownloadFile,
|
onDownloadFile,
|
||||||
isSupported = true,
|
isSupported = true,
|
||||||
@ -193,7 +193,7 @@ const FileEditorThumbnail = ({
|
|||||||
if (!isSupported) return;
|
if (!isSupported) return;
|
||||||
// Clear error state if file has an error (click to clear error)
|
// Clear error state if file has an error (click to clear error)
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
try { fileActions.clearFileError(file.id); } catch {}
|
try { fileActions.clearFileError(file.id); } catch (_e) { void _e; }
|
||||||
}
|
}
|
||||||
onToggleFile(file.id);
|
onToggleFile(file.id);
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ import PageEditorControls from '../pageEditor/PageEditorControls';
|
|||||||
import Viewer from '../viewer/Viewer';
|
import Viewer from '../viewer/Viewer';
|
||||||
import LandingPage from '../shared/LandingPage';
|
import LandingPage from '../shared/LandingPage';
|
||||||
import Footer from '../shared/Footer';
|
import Footer from '../shared/Footer';
|
||||||
|
import DismissAllErrorsButton from '../shared/DismissAllErrorsButton';
|
||||||
|
|
||||||
// No props needed - component uses contexts directly
|
// No props needed - component uses contexts directly
|
||||||
export default function Workbench() {
|
export default function Workbench() {
|
||||||
@ -151,6 +152,9 @@ export default function Workbench() {
|
|||||||
selectedToolKey={selectedToolId}
|
selectedToolKey={selectedToolId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Dismiss All Errors Button */}
|
||||||
|
<DismissAllErrorsButton />
|
||||||
|
|
||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
<Box
|
<Box
|
||||||
className="flex-1 min-h-0 relative z-10 workbench-scrollable "
|
className="flex-1 min-h-0 relative z-10 workbench-scrollable "
|
||||||
|
51
frontend/src/components/shared/DismissAllErrorsButton.tsx
Normal file
51
frontend/src/components/shared/DismissAllErrorsButton.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, Group } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFileState } from '../../contexts/FileContext';
|
||||||
|
import { useFileActions } from '../../contexts/file/fileHooks';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
|
interface DismissAllErrorsButtonProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DismissAllErrorsButton: React.FC<DismissAllErrorsButtonProps> = ({ className }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { state } = useFileState();
|
||||||
|
const { actions } = useFileActions();
|
||||||
|
|
||||||
|
// Check if there are any files in error state
|
||||||
|
const hasErrors = state.ui.errorFileIds.length > 0;
|
||||||
|
|
||||||
|
// Don't render if there are no errors
|
||||||
|
if (!hasErrors) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDismissAllErrors = () => {
|
||||||
|
actions.clearAllFileErrors();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group className={className}>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
leftSection={<CloseIcon fontSize="small" />}
|
||||||
|
onClick={handleDismissAllErrors}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '1rem',
|
||||||
|
right: '1rem',
|
||||||
|
zIndex: 1000,
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('error.dismissAllErrors', 'Dismiss All Errors')} ({state.ui.errorFileIds.length})
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DismissAllErrorsButton;
|
@ -72,7 +72,7 @@ export default function RightRail() {
|
|||||||
const allIds = state.files.ids;
|
const allIds = state.files.ids;
|
||||||
setSelectedFiles(allIds);
|
setSelectedFiles(allIds);
|
||||||
// Clear any previous error flags when selecting all
|
// Clear any previous error flags when selecting all
|
||||||
try { fileActions.clearAllFileErrors(); } catch {}
|
try { fileActions.clearAllFileErrors(); } catch (_e) { void _e; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ export default function RightRail() {
|
|||||||
if (currentView === 'fileEditor' || currentView === 'viewer') {
|
if (currentView === 'fileEditor' || currentView === 'viewer') {
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
// Clear any previous error flags when deselecting all
|
// Clear any previous error flags when deselecting all
|
||||||
try { fileActions.clearAllFileErrors(); } catch {}
|
try { fileActions.clearAllFileErrors(); } catch (_e) { void _e; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentView === 'pageEditor') {
|
if (currentView === 'pageEditor') {
|
||||||
|
@ -57,7 +57,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|||||||
progress: normalizeProgress(options.progressBarPercentage),
|
progress: normalizeProgress(options.progressBarPercentage),
|
||||||
justCompleted: false,
|
justCompleted: false,
|
||||||
expandable: hasButton ? false : (options.expandable !== false),
|
expandable: hasButton ? false : (options.expandable !== false),
|
||||||
isExpanded: hasButton ? true : (options.expandable === false ? true : false),
|
isExpanded: hasButton ? true : (options.expandable === false ? true : (options.alertType === 'error' ? true : false)),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
} as ToastInstance;
|
} as ToastInstance;
|
||||||
setToasts(prev => {
|
setToasts(prev => {
|
||||||
|
@ -61,7 +61,11 @@ export const useToolApiCalls = <TParams = void>() => {
|
|||||||
if (empty) {
|
if (empty) {
|
||||||
console.warn('[processFiles] Empty output treated as failure', { name: file.name });
|
console.warn('[processFiles] Empty output treated as failure', { name: file.name });
|
||||||
failedFiles.push(file.name);
|
failedFiles.push(file.name);
|
||||||
try { (markFileError as any)?.((file as any).fileId); } catch {}
|
try {
|
||||||
|
(markFileError as any)?.((file as any).fileId);
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('markFileError', e);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
processedFiles.push(...responseFiles);
|
processedFiles.push(...responseFiles);
|
||||||
@ -76,7 +80,11 @@ export const useToolApiCalls = <TParams = void>() => {
|
|||||||
console.error('[processFiles] Failed', { name: file.name, error });
|
console.error('[processFiles] Failed', { name: file.name, error });
|
||||||
failedFiles.push(file.name);
|
failedFiles.push(file.name);
|
||||||
// mark errored file so UI can highlight
|
// mark errored file so UI can highlight
|
||||||
try { (markFileError as any)?.((file as any).fileId); } catch {}
|
try {
|
||||||
|
(markFileError as any)?.((file as any).fileId);
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('markFileError', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { useToolState, type ProcessingProgress } from './useToolState';
|
|||||||
import { useToolApiCalls, type ApiCallsConfig } from './useToolApiCalls';
|
import { useToolApiCalls, type ApiCallsConfig } from './useToolApiCalls';
|
||||||
import { useToolResources } from './useToolResources';
|
import { useToolResources } from './useToolResources';
|
||||||
import { extractErrorMessage } from '../../../utils/toolErrorHandler';
|
import { extractErrorMessage } from '../../../utils/toolErrorHandler';
|
||||||
import { StirlingFile, extractFiles, FileId, StirlingFileStub, createStirlingFile, createNewStirlingFileStub } from '../../../types/fileContext';
|
import { StirlingFile, extractFiles, FileId, StirlingFileStub, createStirlingFile } from '../../../types/fileContext';
|
||||||
import { FILE_EVENTS } from '../../../services/errorUtils';
|
import { FILE_EVENTS } from '../../../services/errorUtils';
|
||||||
import { ResponseHandler } from '../../../utils/toolResponseProcessor';
|
import { ResponseHandler } from '../../../utils/toolResponseProcessor';
|
||||||
import { createChildStub, generateProcessedFileMetadata } from '../../../contexts/file/fileActions';
|
import { createChildStub, generateProcessedFileMetadata } from '../../../contexts/file/fileActions';
|
||||||
@ -177,7 +177,9 @@ export const useToolOperation = <TParams>(
|
|||||||
for (const f of zeroByteFiles) {
|
for (const f of zeroByteFiles) {
|
||||||
(fileActions.markFileError as any)((f as any).fileId);
|
(fileActions.markFileError as any)((f as any).fileId);
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch (e) {
|
||||||
|
console.log('markFileError', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const validFiles = selectedFiles.filter(file => (file as any)?.size > 0);
|
const validFiles = selectedFiles.filter(file => (file as any)?.size > 0);
|
||||||
if (validFiles.length === 0) {
|
if (validFiles.length === 0) {
|
||||||
@ -298,15 +300,15 @@ export const useToolOperation = <TParams>(
|
|||||||
const okSet = new Set((successSourceIds as unknown as string[]) || []);
|
const okSet = new Set((successSourceIds as unknown as string[]) || []);
|
||||||
// Clear errors on successes
|
// Clear errors on successes
|
||||||
for (const okId of okSet) {
|
for (const okId of okSet) {
|
||||||
try { (fileActions.clearFileError as any)(okId); } catch {}
|
try { (fileActions.clearFileError as any)(okId); } catch (_e) { void _e; }
|
||||||
}
|
}
|
||||||
// Mark errors on inputs that didn't succeed
|
// Mark errors on inputs that didn't succeed
|
||||||
for (const id of allInputIds) {
|
for (const id of allInputIds) {
|
||||||
if (!okSet.has(id)) {
|
if (!okSet.has(id)) {
|
||||||
try { (fileActions.markFileError as any)(id); } catch {}
|
try { (fileActions.markFileError as any)(id); } catch (_e) { void _e; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch (_e) { void _e; }
|
||||||
|
|
||||||
if (externalErrorFileIds.length > 0) {
|
if (externalErrorFileIds.length > 0) {
|
||||||
// If backend told us which sources failed, prefer that mapping
|
// If backend told us which sources failed, prefer that mapping
|
||||||
@ -318,7 +320,7 @@ export const useToolOperation = <TParams>(
|
|||||||
for (const badId of externalErrorFileIds) {
|
for (const badId of externalErrorFileIds) {
|
||||||
(fileActions.markFileError as any)(badId);
|
(fileActions.markFileError as any)(badId);
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch (_e) { void _e; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processedFiles.length > 0) {
|
if (processedFiles.length > 0) {
|
||||||
@ -426,14 +428,14 @@ export const useToolOperation = <TParams>(
|
|||||||
}
|
}
|
||||||
if (ids && ids.length > 0) {
|
if (ids && ids.length > 0) {
|
||||||
for (const badId of ids) {
|
for (const badId of ids) {
|
||||||
try { (fileActions.markFileError as any)(badId); } catch {}
|
try { (fileActions.markFileError as any)(badId); } catch (_e) { void _e; }
|
||||||
}
|
}
|
||||||
actions.setStatus('Some files could not be processed');
|
actions.setStatus('Process failed due to invalid/corrupted file(s)');
|
||||||
// Avoid duplicating toast messaging here
|
// Avoid duplicating toast messaging here
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch (_e) { void _e; }
|
||||||
|
|
||||||
const errorMessage = config.getErrorMessage?.(error) || extractErrorMessage(error);
|
const errorMessage = config.getErrorMessage?.(error) || extractErrorMessage(error);
|
||||||
actions.setError(errorMessage);
|
actions.setError(errorMessage);
|
||||||
|
@ -25,7 +25,7 @@ function titleForStatus(status?: number): string {
|
|||||||
function extractAxiosErrorMessage(error: any): { title: string; body: string } {
|
function extractAxiosErrorMessage(error: any): { title: string; body: string } {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
const status = error.response?.status;
|
const status = error.response?.status;
|
||||||
const statusText = error.response?.statusText || '';
|
const _statusText = error.response?.statusText || '';
|
||||||
let parsed: any = undefined;
|
let parsed: any = undefined;
|
||||||
const raw = error.response?.data;
|
const raw = error.response?.data;
|
||||||
if (typeof raw === 'string') {
|
if (typeof raw === 'string') {
|
||||||
@ -49,20 +49,37 @@ function extractAxiosErrorMessage(error: any): { title: string; body: string } {
|
|||||||
if (typeof raw === 'string') return raw;
|
if (typeof raw === 'string') return raw;
|
||||||
try { return JSON.stringify(data); } catch { return ''; }
|
try { return JSON.stringify(data); } catch { return ''; }
|
||||||
})();
|
})();
|
||||||
const bodyMsg = isUnhelpfulMessage(body) ? FRIENDLY_FALLBACK : body;
|
const ids = extractIds();
|
||||||
const title = titleForStatus(status);
|
const title = titleForStatus(status);
|
||||||
|
if (ids && ids.length > 0) {
|
||||||
|
return { title, body: 'Process failed due to invalid/corrupted file(s)' };
|
||||||
|
}
|
||||||
|
if (status === 422) {
|
||||||
|
const fallbackMsg = 'Process failed due to invalid/corrupted file(s)';
|
||||||
|
const bodyMsg = isUnhelpfulMessage(body) ? fallbackMsg : body;
|
||||||
|
return { title, body: bodyMsg };
|
||||||
|
}
|
||||||
|
const bodyMsg = isUnhelpfulMessage(body) ? FRIENDLY_FALLBACK : body;
|
||||||
return { title, body: bodyMsg };
|
return { title, body: bodyMsg };
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const msg = (error?.message || String(error)) as string;
|
const msg = (error?.message || String(error)) as string;
|
||||||
return { title: 'Network error', body: isUnhelpfulMessage(msg) ? FRIENDLY_FALLBACK : msg };
|
return { title: 'Network error', body: isUnhelpfulMessage(msg) ? FRIENDLY_FALLBACK : msg };
|
||||||
} catch {
|
} catch (e) {
|
||||||
|
// ignore extraction errors
|
||||||
|
console.debug('extractAxiosErrorMessage', e);
|
||||||
return { title: 'Network error', body: FRIENDLY_FALLBACK };
|
return { title: 'Network error', body: FRIENDLY_FALLBACK };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install Axios response error interceptor
|
// Install Axios response error interceptor (guard against double-registration in HMR)
|
||||||
axios.interceptors.response.use(
|
const __globalAny = (typeof window !== 'undefined' ? (window as any) : undefined);
|
||||||
|
if (__globalAny?.__SPDF_HTTP_ERR_INTERCEPTOR_ID !== undefined) {
|
||||||
|
try { axios.interceptors.response.eject(__globalAny.__SPDF_HTTP_ERR_INTERCEPTOR_ID); } catch (_e) { void _e; }
|
||||||
|
}
|
||||||
|
const __recentSpecialByEndpoint: Record<string, number> = (__globalAny?.__SPDF_RECENT_SPECIAL || {});
|
||||||
|
const __SPECIAL_SUPPRESS_MS = 1500; // brief window to suppress generic duplicate after special toast
|
||||||
|
const __INTERCEPTOR_ID__ = axios.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
async (error) => {
|
async (error) => {
|
||||||
const { title, body } = extractAxiosErrorMessage(error);
|
const { title, body } = extractAxiosErrorMessage(error);
|
||||||
@ -71,12 +88,34 @@ axios.interceptors.response.use(
|
|||||||
const raw = (error?.response?.data) as any;
|
const raw = (error?.response?.data) as any;
|
||||||
const data = await normalizeAxiosErrorData(raw);
|
const data = await normalizeAxiosErrorData(raw);
|
||||||
const ids = extractErrorFileIds(data);
|
const ids = extractErrorFileIds(data);
|
||||||
if (ids && ids.length > 0) broadcastErroredFiles(ids);
|
if (ids && ids.length > 0) {
|
||||||
} catch {}
|
broadcastErroredFiles(ids);
|
||||||
|
}
|
||||||
|
} catch (_e) { void _e; }
|
||||||
|
|
||||||
|
// Generic-vs-special dedupe by endpoint
|
||||||
|
const url: string | undefined = error?.config?.url;
|
||||||
|
const status: number | undefined = error?.response?.status;
|
||||||
|
const now = Date.now();
|
||||||
|
const isSpecial = status === 422 || /Failed files:/.test(body) || /invalid\/corrupted file\(s\)/i.test(body);
|
||||||
|
if (isSpecial && url) {
|
||||||
|
__recentSpecialByEndpoint[url] = now;
|
||||||
|
if (__globalAny) __globalAny.__SPDF_RECENT_SPECIAL = __recentSpecialByEndpoint;
|
||||||
|
}
|
||||||
|
if (!isSpecial && url) {
|
||||||
|
const last = __recentSpecialByEndpoint[url] || 0;
|
||||||
|
if (now - last < __SPECIAL_SUPPRESS_MS) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
alert({ alertType: 'error', title, body, expandable: true, isPersistentPopup: false });
|
alert({ alertType: 'error', title, body, expandable: true, isPersistentPopup: false });
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
if (__globalAny) {
|
||||||
|
__globalAny.__SPDF_HTTP_ERR_INTERCEPTOR_ID = __INTERCEPTOR_ID__;
|
||||||
|
}
|
||||||
|
|
||||||
export async function apiFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
export async function apiFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||||
const res = await fetch(input, { credentials: init?.credentials ?? 'include', ...init });
|
const res = await fetch(input, { credentials: init?.credentials ?? 'include', ...init });
|
||||||
|
@ -12,7 +12,7 @@ export const extractErrorMessage = (error: any): string => {
|
|||||||
if (error.message) {
|
if (error.message) {
|
||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
return 'Operation failed';
|
return 'There was an error processing your request.';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user