Skip to content

Commit 2329a1a

Browse files
committed
feat: Add Processing page and integrate with UploadModal for post-upload navigation
1 parent fd4482d commit 2329a1a

File tree

5 files changed

+202
-0
lines changed

5 files changed

+202
-0
lines changed

frontend/src/common/components/Router/TabNavigation.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import ChatPage from 'pages/Chat/ChatPage';
1818
import UploadPage from 'pages/Upload/UploadPage';
1919
import ReportDetailPage from 'pages/Reports/ReportDetailPage';
2020
import ReportsListPage from 'pages/Reports/ReportsListPage';
21+
import Processing from 'pages/Processing/Processing';
2122

2223
/**
2324
* The `TabNavigation` component provides a router outlet for all of the
@@ -90,6 +91,9 @@ const TabNavigation = (): JSX.Element => {
9091
<Route exact path="/tabs/reports/:reportId">
9192
<ReportDetailPage />
9293
</Route>
94+
<Route exact path="/tabs/processing">
95+
<Processing />
96+
</Route>
9397
<Route exact path="/">
9498
<Redirect to="/tabs/home" />
9599
</Route>

frontend/src/common/components/Upload/UploadModal.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { MedicalReport } from '../../models/medicalReport';
1515
import './UploadModal.scss';
1616
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
1717
import { faCircleXmark } from '@fortawesome/free-regular-svg-icons';
18+
import { useHistory } from 'react-router';
19+
import { useTimeout } from '../../hooks/useTimeout';
1820

1921
export interface UploadModalProps {
2022
isOpen: boolean;
@@ -25,6 +27,8 @@ export interface UploadModalProps {
2527

2628
const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): JSX.Element => {
2729
const { t } = useTranslation();
30+
const history = useHistory();
31+
const { setTimeout } = useTimeout();
2832
const fileInputRef = useRef<HTMLInputElement>(null);
2933
// Track the upload result to use when the user closes the success screen
3034
const [uploadResult, setUploadResult] = useState<MedicalReport | null>(null);
@@ -45,6 +49,17 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
4549
// Override onUploadComplete to store the result and not call the parent immediately
4650
onUploadComplete: (result) => {
4751
setUploadResult(result);
52+
53+
// Automatically redirect to processing screen after 2 seconds
54+
setTimeout(() => {
55+
reset();
56+
onClose();
57+
if (onUploadComplete) {
58+
onUploadComplete(result);
59+
}
60+
// Navigate to the processing tab
61+
history.push('/tabs/processing');
62+
}, 2000);
4863
}
4964
});
5065

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useCallback, useEffect, useRef } from 'react';
2+
3+
/**
4+
* Custom hook for handling setTimeout with cleanup
5+
*/
6+
export const useTimeout = () => {
7+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
8+
9+
// Clear the timeout when component unmounts or when called manually
10+
const clearTimeout = useCallback(() => {
11+
if (timeoutRef.current) {
12+
window.clearTimeout(timeoutRef.current);
13+
timeoutRef.current = null;
14+
}
15+
}, []);
16+
17+
// Set a new timeout
18+
const setTimeout = useCallback((callback: () => void, delay: number) => {
19+
// Clear any existing timeout first
20+
clearTimeout();
21+
// Set the new timeout
22+
timeoutRef.current = window.setTimeout(callback, delay);
23+
}, [clearTimeout]);
24+
25+
// Clean up on unmount
26+
useEffect(() => {
27+
return clearTimeout;
28+
}, [clearTimeout]);
29+
30+
return { setTimeout, clearTimeout };
31+
};
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
.processing-page {
2+
--background: #1c1c1e;
3+
4+
&__container {
5+
background-color: white;
6+
border-radius: 32px;
7+
height: 100%;
8+
margin: 0;
9+
padding: 24px;
10+
display: flex;
11+
flex-direction: column;
12+
position: relative;
13+
}
14+
15+
&__header {
16+
display: flex;
17+
flex-direction: column;
18+
margin-top: 20px;
19+
}
20+
21+
&__avatar-wrapper {
22+
margin-bottom: 20px;
23+
24+
// Styles for the Avatar component - adjusted size to match design
25+
.ls-avatar {
26+
--size: 60px !important;
27+
margin: 0;
28+
29+
// This makes sure even round avatars are sized correctly
30+
&.ls-avatar--round {
31+
width: var(--size);
32+
height: var(--size);
33+
}
34+
}
35+
}
36+
37+
&__title {
38+
margin-bottom: 40px;
39+
}
40+
41+
&__subtitle {
42+
font-size: 20px;
43+
color: #9BA1AB;
44+
margin: 0 0 5px 0;
45+
font-weight: 400;
46+
}
47+
48+
&__heading {
49+
font-size: 36px;
50+
font-weight: 600;
51+
color: #394150;
52+
margin: 0;
53+
}
54+
55+
&__animation {
56+
flex: 1;
57+
display: flex;
58+
align-items: center;
59+
justify-content: center;
60+
61+
&-circle {
62+
// Adjusted animation size to match design
63+
width: 180px;
64+
height: 180px;
65+
border-radius: 50%;
66+
background: radial-gradient(circle at 30% 30%, #4f7dff 10%, #c97eff 40%, #ff8afc 80%);
67+
box-shadow:
68+
0 0 40px rgba(201, 126, 255, 0.5),
69+
0 0 80px rgba(201, 126, 255, 0.3);
70+
position: relative;
71+
overflow: hidden;
72+
animation: pulse 3s infinite ease-in-out;
73+
74+
&::after {
75+
content: "";
76+
position: absolute;
77+
top: 0;
78+
left: 0;
79+
right: 0;
80+
bottom: 0;
81+
background: linear-gradient(135deg, rgba(255,255,255,0.4) 0%, transparent 50%);
82+
border-radius: 50%;
83+
}
84+
}
85+
}
86+
}
87+
88+
// Animation for the pulse effect
89+
@keyframes pulse {
90+
0% {
91+
transform: scale(0.95);
92+
opacity: 0.8;
93+
}
94+
50% {
95+
transform: scale(1);
96+
opacity: 1;
97+
}
98+
100% {
99+
transform: scale(0.95);
100+
opacity: 0.8;
101+
}
102+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { IonContent, IonPage } from '@ionic/react';
2+
import { useCurrentUser } from '../../common/hooks/useAuth';
3+
import Avatar from '../../common/components/Icon/Avatar';
4+
import './Processing.scss';
5+
6+
/**
7+
* Processing page that shows while the system analyzes uploaded documents
8+
* This page automatically displays after a successful upload
9+
*/
10+
const Processing: React.FC = () => {
11+
const currentUser = useCurrentUser();
12+
const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'Wesley';
13+
14+
return (
15+
<IonPage className="processing-page">
16+
<IonContent fullscreen>
17+
<div className="processing-page__container">
18+
{/* Header with avatar */}
19+
<div className="processing-page__header">
20+
<div className="processing-page__avatar-wrapper">
21+
<Avatar
22+
value={currentUser?.name || currentUser?.email || ''}
23+
size="large"
24+
shape="round"
25+
testid="processing-user-avatar"
26+
/>
27+
</div>
28+
29+
{/* Title section */}
30+
<div className="processing-page__title">
31+
<p className="processing-page__subtitle">
32+
Just a few seconds, {firstName}!
33+
</p>
34+
<h1 className="processing-page__heading">Processing Data...</h1>
35+
</div>
36+
</div>
37+
38+
{/* Animation circle */}
39+
<div className="processing-page__animation">
40+
<div className="processing-page__animation-circle"></div>
41+
</div>
42+
43+
{/* We don't need to include the tab bar here since it's global */}
44+
</div>
45+
</IonContent>
46+
</IonPage>
47+
);
48+
};
49+
50+
export default Processing;

0 commit comments

Comments
 (0)