Skip to content

Commit bfad64f

Browse files
authored
Merge pull request #90 from ModusCreateOrg/ADE-66
[ADE-66] Add error handling and animation components to ProcessingPage for improved user feedback
2 parents fba1a87 + 664ac0a commit bfad64f

File tree

4 files changed

+241
-75
lines changed

4 files changed

+241
-75
lines changed

frontend/src/pages/Processing/ProcessingPage.scss

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,103 @@
8383
}
8484
}
8585
}
86+
87+
&__error-container {
88+
display: flex;
89+
flex-direction: column;
90+
align-items: center;
91+
text-align: center;
92+
padding: 2rem;
93+
max-width: 500px;
94+
margin: 0 auto;
95+
}
96+
97+
&__error-header {
98+
margin-bottom: 1.5rem;
99+
}
100+
101+
&__error-oops {
102+
font-size: 1.25rem;
103+
color: var(--ion-color-medium);
104+
margin-bottom: 0.5rem;
105+
}
106+
107+
&__error-title {
108+
font-size: 2rem;
109+
font-weight: 600;
110+
margin: 0;
111+
color: var(--ion-color-dark);
112+
}
113+
114+
&__error-icon {
115+
margin: 1.5rem 0;
116+
}
117+
118+
&__error-icon-circle {
119+
width: 120px;
120+
height: 120px;
121+
background-color: rgba(196, 30, 58, 0.1);
122+
border-radius: 50%;
123+
display: flex;
124+
align-items: center;
125+
justify-content: center;
126+
}
127+
128+
&__error-subheading {
129+
font-size: 1.5rem;
130+
font-weight: 600;
131+
color: var(--ion-color-dark);
132+
margin-bottom: 1rem;
133+
}
134+
135+
&__error-message {
136+
font-size: 1rem;
137+
color: var(--ion-color-medium);
138+
line-height: 1.5;
139+
margin-bottom: 2rem;
140+
max-width: 400px;
141+
}
142+
143+
&__error-actions {
144+
display: flex;
145+
gap: 1rem;
146+
width: 100%;
147+
max-width: 400px;
148+
}
149+
150+
&__retry-btn {
151+
flex: 1;
152+
padding: 0.75rem 1rem;
153+
border-radius: 2rem;
154+
background: transparent;
155+
border: 1px solid var(--ion-color-primary);
156+
color: var(--ion-color-primary);
157+
font-weight: 500;
158+
font-size: 1rem;
159+
cursor: pointer;
160+
transition: background-color 0.2s, color 0.2s;
161+
162+
&:hover {
163+
background-color: var(--ion-color-primary-tint);
164+
}
165+
}
166+
167+
&__upload-btn {
168+
flex: 1;
169+
padding: 0.75rem 1rem;
170+
border-radius: 2rem;
171+
background: var(--ion-color-primary);
172+
border: none;
173+
color: white;
174+
font-weight: 500;
175+
font-size: 1rem;
176+
cursor: pointer;
177+
transition: background-color 0.2s;
178+
179+
&:hover {
180+
background-color: var(--ion-color-primary-shade);
181+
}
182+
}
86183
}
87184

88185
// Animation for the pulse effect

frontend/src/pages/Processing/ProcessingPage.tsx

Lines changed: 56 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { useEffect, useState, useRef } from 'react';
66
import { useAxios } from '../../common/hooks/useAxios';
77
import './ProcessingPage.scss';
88
import { getAuthConfig } from 'common/api/reportService';
9+
import ProcessingError from './components/ProcessingError';
10+
import ProcessingAnimation from './components/ProcessingAnimation';
11+
912
const API_URL = import.meta.env.VITE_BASE_URL_API || '';
1013

1114
/**
@@ -28,6 +31,13 @@ const ProcessingPage: React.FC = () => {
2831
const location = useLocation<{ reportId: string }>();
2932
const reportId = location.state?.reportId;
3033

34+
const clearStatusCheckInterval = () => {
35+
if (statusCheckIntervalRef.current) {
36+
window.clearInterval(statusCheckIntervalRef.current);
37+
statusCheckIntervalRef.current = null;
38+
}
39+
};
40+
3141
// Check the status of the report processing
3242
const checkReportStatus = async () => {
3343
if (!reportId) return;
@@ -38,86 +48,79 @@ const ProcessingPage: React.FC = () => {
3848
await getAuthConfig(),
3949
);
4050

41-
console.log('Report status:', response.data);
51+
const data = response.data;
4252

4353
// If processing is complete, clear the interval and redirect to the report page
44-
if (response.data.isComplete) {
54+
if (data.isComplete) {
4555
setIsProcessing(false);
4656

4757
// Clear the interval
48-
if (statusCheckIntervalRef.current) {
49-
window.clearInterval(statusCheckIntervalRef.current);
50-
statusCheckIntervalRef.current = null;
51-
}
58+
clearStatusCheckInterval();
5259

5360
console.log('Processing complete');
5461

5562
// Redirect to report detail page
5663
history.push(`/tabs/reports/${reportId}`);
64+
} else if (data.status === 'failed') {
65+
throw new Error('Processing failed');
5766
}
5867
} catch (error) {
68+
// Clear the interval on error
69+
clearStatusCheckInterval();
70+
5971
console.error('Error checking report status:', error);
60-
setProcessingError('Failed to check the status of the report. Please try again.');
72+
setProcessingError('An error occurred while processing the report. Please try again.');
6173
setIsProcessing(false);
74+
}
75+
};
6276

63-
// Clear the interval on error
64-
if (statusCheckIntervalRef.current) {
65-
window.clearInterval(statusCheckIntervalRef.current);
66-
statusCheckIntervalRef.current = null;
67-
}
77+
// Process file function - moved outside useEffect to make it callable from buttons
78+
const processFile = async () => {
79+
if (!reportId) {
80+
return;
81+
}
82+
83+
try {
84+
// Send POST request to backend API
85+
await axios.post(
86+
`${API_URL}/api/document-processor/process-file`,
87+
{ reportId },
88+
await getAuthConfig(),
89+
);
90+
91+
// Start checking the status every 2 seconds
92+
statusCheckIntervalRef.current = window.setInterval(checkReportStatus, 2000);
93+
} catch (error) {
94+
console.error('Error processing file:', error);
95+
setProcessingError('Failed to process the file. Please try again.');
96+
setIsProcessing(false);
6897
}
6998
};
7099

100+
// Handle retry attempt
101+
const handleRetry = () => {
102+
// Reset error state and try processing the same file again
103+
setProcessingError(null);
104+
setIsProcessing(true);
105+
hasInitiatedProcessing.current = false;
106+
processFile();
107+
};
108+
71109
// Send the API request when component mounts
72110
useEffect(() => {
73111
// Use ref to ensure this effect runs only once for the core logic
74112
if (hasInitiatedProcessing.current) {
75113
return;
76114
}
77115

78-
if (!reportId) {
79-
setProcessingError('No report ID provided');
80-
setIsProcessing(false);
81-
hasInitiatedProcessing.current = true; // Mark as initiated even on error
82-
return;
83-
}
84-
85116
hasInitiatedProcessing.current = true; // Mark as initiated before fetching
86117

87-
const processFile = async () => {
88-
try {
89-
// Send POST request to backend API
90-
const response = await axios.post(
91-
`${API_URL}/api/document-processor/process-file`,
92-
{ reportId },
93-
await getAuthConfig(),
94-
);
95-
96-
console.log('File processing started:', response.data);
97-
98-
// Start checking the status every 2 seconds
99-
statusCheckIntervalRef.current = window.setInterval(checkReportStatus, 2000);
100-
101-
// Run the first status check immediately
102-
checkReportStatus();
103-
} catch (error) {
104-
console.error('Error processing file:', error);
105-
setProcessingError('Failed to process the file. Please try again.');
106-
setIsProcessing(false);
107-
}
108-
};
109-
110118
processFile();
111119

112120
// Clean up the interval when the component unmounts
113-
return () => {
114-
if (statusCheckIntervalRef.current) {
115-
window.clearInterval(statusCheckIntervalRef.current);
116-
}
117-
};
121+
return clearStatusCheckInterval;
118122
}, [reportId, location, history]);
119123

120-
121124
return (
122125
<IonPage className="processing-page">
123126
<IonContent fullscreen>
@@ -132,41 +135,19 @@ const ProcessingPage: React.FC = () => {
132135
testid="processing-user-avatar"
133136
/>
134137
</div>
135-
136-
{/* Title section */}
137-
<div className="processing-page__title">
138-
<p className="processing-page__subtitle">
139-
Just a few seconds{firstName && ', ' + firstName}!
140-
</p>
141-
<h1 className="processing-page__heading">
142-
{processingError ? 'Processing Error' : 'Processing Data...'}
143-
</h1>
144-
{processingError && <p className="processing-page__error">{processingError}</p>}
145-
</div>
146138
</div>
147139

148-
{/* Animation circle */}
149-
{isProcessing && (
150-
<div className="processing-page__animation">
151-
<div className="processing-page__animation-circle"></div>
152-
</div>
153-
)}
140+
{/* Processing animation */}
141+
{isProcessing && <ProcessingAnimation firstName={firstName} />}
154142

155-
{/* Error state - show retry button */}
143+
{/* Error state - shows when processing fails */}
156144
{processingError && (
157-
<div className="processing-page__error-actions">
158-
<button
159-
className="processing-page__retry-btn"
160-
onClick={() => history.push('/tabs/upload')}
161-
>
162-
Try Again
163-
</button>
164-
</div>
145+
<ProcessingError errorMessage={processingError} onRetry={handleRetry} />
165146
)}
166147
</div>
167148
</IonContent>
168149
</IonPage>
169150
);
170151
};
171152

172-
export default ProcessingPage;
153+
export default ProcessingPage;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import '../ProcessingPage.scss';
2+
3+
interface ProcessingAnimationProps {
4+
firstName?: string;
5+
}
6+
7+
/**
8+
* Component that displays processing animation and message
9+
*/
10+
const ProcessingAnimation: React.FC<ProcessingAnimationProps> = ({ firstName }) => {
11+
return (
12+
<>
13+
<div className="processing-page__title">
14+
<p className="processing-page__subtitle">
15+
Just a few seconds{firstName && ', ' + firstName}!
16+
</p>
17+
<h1 className="processing-page__heading">Processing Data...</h1>
18+
</div>
19+
<div className="processing-page__animation">
20+
<div className="processing-page__animation-circle"></div>
21+
</div>
22+
</>
23+
);
24+
};
25+
26+
export default ProcessingAnimation;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useHistory } from 'react-router-dom';
2+
import '../ProcessingPage.scss';
3+
4+
interface ProcessingErrorProps {
5+
errorMessage: string;
6+
onRetry: () => void;
7+
}
8+
9+
/**
10+
* Component that displays processing error information and actions
11+
*/
12+
const ProcessingError: React.FC<ProcessingErrorProps> = ({ errorMessage, onRetry }) => {
13+
const history = useHistory();
14+
15+
return (
16+
<div className="processing-page__error-container">
17+
<div className="processing-page__error-header">
18+
<p className="processing-page__error-oops">Oops! ...</p>
19+
<h2 className="processing-page__error-title">Problem detected</h2>
20+
</div>
21+
22+
<div className="processing-page__error-icon">
23+
<div className="processing-page__error-icon-circle">
24+
<svg
25+
width="48"
26+
height="48"
27+
viewBox="0 0 48 48"
28+
fill="none"
29+
xmlns="http://www.w3.org/2000/svg"
30+
>
31+
<path
32+
d="M24 4C12.96 4 4 12.96 4 24C4 35.04 12.96 44 24 44C35.04 44 44 35.04 44 24C44 12.96 35.04 4 24 4ZM26 34H22V30H26V34ZM26 26H22V14H26V26Z"
33+
fill="#C41E3A"
34+
/>
35+
</svg>
36+
</div>
37+
</div>
38+
39+
<h3 className="processing-page__error-subheading">Processing Error</h3>
40+
41+
<p className="processing-page__error-message">
42+
{errorMessage ||
43+
'There was a problem processing your uploaded file. Please try again or upload another.'}
44+
</p>
45+
46+
<div className="processing-page__error-actions">
47+
<button className="processing-page__retry-btn" onClick={onRetry}>
48+
Try again
49+
</button>
50+
51+
<button
52+
className="processing-page__upload-btn"
53+
onClick={() => history.push('/tabs/upload')}
54+
>
55+
New Upload
56+
</button>
57+
</div>
58+
</div>
59+
);
60+
};
61+
62+
export default ProcessingError;

0 commit comments

Comments
 (0)