Skip to content

Commit 03755de

Browse files
Enhance UploadModal with error handling and new icon
- Added error message display in UploadModal for better user feedback. - Introduced faCircleXmark icon for error indication. - Updated UploadModal.scss for new error message styling. - Adjusted UploadModal.tsx to render error state correctly. - Added new error messages in English, Spanish, and French for file size and type validation. - Included a new image asset for upload error representation.
1 parent 03a40de commit 03755de

File tree

10 files changed

+62
-82
lines changed

10 files changed

+62
-82
lines changed
114 KB
Loading

frontend/src/common/components/Icon/Icon.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
faBuilding,
88
faCalendar,
99
faCircleInfo,
10+
faCircleXmark,
1011
faEnvelope,
1112
faHouse,
1213
faLink,
@@ -53,6 +54,7 @@ export type IconName =
5354
| 'building'
5455
| 'calendar'
5556
| 'circleInfo'
57+
| 'circleXmark'
5658
| 'comment'
5759
| 'envelope'
5860
| 'fileLines'
@@ -98,6 +100,7 @@ const solidIcons: Record<IconName, IconProp> = {
98100
building: faBuilding,
99101
calendar: faCalendar,
100102
circleInfo: faCircleInfo,
103+
circleXmark: faCircleXmark,
101104
comment: faComment,
102105
envelope: faEnvelope,
103106
fileLines: faSolidFileLines,
@@ -130,7 +133,8 @@ const regularIcons: Partial<Record<IconName, IconProp>> = {
130133
comment: faRegularComment,
131134
fileLines: faRegularFileLines,
132135
user: faRegularUser,
133-
bookmark: faRegularBookmark
136+
bookmark: faRegularBookmark,
137+
circleXmark: faCircleXmark,
134138
};
135139

136140
/**

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

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.upload-modal {
2-
--height: 60vh;
2+
--height: 65vh;
33
--width: 100%;
44
--border-radius: 1rem;
55
--background: var(--ion-color-primary);
@@ -11,10 +11,26 @@
1111
border-radius: 1rem;
1212
overflow: hidden;
1313
}
14+
15+
&__error-message {
16+
border: 1px solid var(--ion-color-danger);
17+
border-radius: 0.5rem;
18+
--background: rgba(var(--ion-color-danger-rgb), 0.2);
19+
color: white;
20+
width: 100%;
21+
22+
ion-label {
23+
font-size: 0.875rem;
24+
}
25+
}
26+
27+
&__error-icon {
28+
color: var(--ion-color-danger);
29+
}
1430

1531
@media (min-width: 768px) {
1632
--width: 28.125rem; // 450px
17-
--height: 60vh; // Use viewport height instead of fixed pixels
33+
--height: 65vh; // Use viewport height instead of fixed pixels
1834
--border-radius: 1rem; // Matching the image example
1935
}
2036

@@ -55,7 +71,7 @@
5571
justify-content: center;
5672
flex: 1;
5773
text-align: center;
58-
margin: 1rem;
74+
margin: 0 1rem;
5975
}
6076

6177
&__drop-area {
@@ -242,23 +258,7 @@
242258
text-align: center;
243259
margin: 1rem 0;
244260
}
245-
246-
&__error-icon {
247-
font-size: 4rem;
248-
color: var(--ion-color-danger);
249-
}
250-
251-
&__error-message {
252-
background-color: rgba(var(--ion-color-danger-rgb), 0.2);
253-
padding: 0.75rem;
254-
border-radius: 0.5rem;
255-
color: white !important;
256-
font-size: 0.875rem;
257-
width: 100%;
258-
max-width: 20rem;
259-
margin: 0 auto 2rem !important;
260-
}
261-
261+
262262
&__close-btn, &__retry-btn {
263263
margin-top: 1rem;
264264
width: 100%;

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

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import {
55
IonButton,
66
IonIcon,
77
IonProgressBar,
8+
IonLabel,
9+
IonItem
810
} from '@ionic/react';
9-
import { closeOutline, cloudUploadOutline, alertCircleOutline, documentOutline, checkmarkOutline } from 'ionicons/icons';
11+
import { closeOutline, cloudUploadOutline, documentOutline, checkmarkOutline } from 'ionicons/icons';
1012
import { useTranslation } from 'react-i18next';
1113
import { UploadStatus, useFileUpload } from '../../hooks/useFileUpload';
1214
import { MedicalReport } from '../../models/medicalReport';
1315
import './UploadModal.scss';
16+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
17+
import { faCircleXmark } from '@fortawesome/free-regular-svg-icons';
1418

1519
export interface UploadModalProps {
1620
isOpen: boolean;
@@ -103,6 +107,14 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
103107
{t('upload.imageSizeLimit')} / {t('upload.pdfSizeLimit')}
104108
</p>
105109
</div>
110+
{error &&
111+
<IonItem className='upload-modal__error-message'>
112+
<div className='upload-modal__error-icon' slot='start'>
113+
<FontAwesomeIcon icon={faCircleXmark} aria-hidden="true"/>
114+
</div>
115+
<IonLabel className="upload-modal__error-label ion-text-wrap">{error}</IonLabel>
116+
</IonItem>
117+
}
106118
<input
107119
type="file"
108120
ref={fileInputRef}
@@ -170,31 +182,6 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
170182
</div>
171183
);
172184

173-
const renderErrorState = () => (
174-
<div className="upload-modal__error">
175-
<IonIcon icon={alertCircleOutline} className="upload-modal__error-icon" />
176-
<h2>{t('upload.uploadFailed')}</h2>
177-
<p className="upload-modal__error-message">{error}</p>
178-
<div className="upload-modal__buttons">
179-
<IonButton
180-
expand="block"
181-
className="upload-modal__retry-btn"
182-
onClick={reset}
183-
>
184-
{t('common.tryAgain')}
185-
</IonButton>
186-
<IonButton
187-
expand="block"
188-
fill="outline"
189-
className="upload-modal__cancel-btn"
190-
onClick={handleClose}
191-
>
192-
{t('common.cancel')}
193-
</IonButton>
194-
</div>
195-
</div>
196-
);
197-
198185
const renderContent = () => {
199186
switch (status) {
200187
case UploadStatus.UPLOADING:
@@ -203,7 +190,7 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
203190
case UploadStatus.SUCCESS:
204191
return renderSuccessState();
205192
case UploadStatus.ERROR:
206-
return renderErrorState();
193+
return renderInitialState();
207194
case UploadStatus.IDLE:
208195
case UploadStatus.VALIDATING:
209196
default:

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

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ vi.mock('@ionic/react', () => ({
6060
<div data-testid={`ion-spinner-${name}`}>Loading...</div>,
6161
IonProgressBar: ({ value }: { value: number }) =>
6262
<div data-testid="ion-progress-bar" data-value={value}>Progress: {value * 100}%</div>,
63+
IonItem: ({ children, className }: { children: React.ReactNode; className?: string }) =>
64+
<div data-testid="ion-item" className={className}>{children}</div>,
65+
IonLabel: ({ children, className }: { children: React.ReactNode; className?: string }) =>
66+
<div data-testid="ion-label" className={className}>{children}</div>,
6367
}));
6468

6569
// Mock the Ionicons
@@ -245,11 +249,8 @@ describe('UploadModal', () => {
245249
render(<UploadModal {...mockProps} />);
246250

247251
// Check for error elements
248-
expect(screen.getByText('upload.uploadFailed')).toBeInTheDocument();
249252
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
250-
expect(screen.getByText('common.tryAgain')).toBeInTheDocument();
251-
expect(screen.getByText('common.cancel')).toBeInTheDocument();
252-
expect(screen.getByTestId('ion-icon-error-icon')).toBeInTheDocument();
253+
253254
});
254255

255256
test('opens the file picker when select file button is clicked', () => {
@@ -346,31 +347,6 @@ describe('UploadModal', () => {
346347
expect(cancelUploadMock).toHaveBeenCalled();
347348
});
348349

349-
test('calls reset when "Try Again" button is clicked in error state', () => {
350-
const resetMock = vi.fn();
351-
352-
(useFileUpload as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
353-
file: null,
354-
status: UploadStatus.ERROR,
355-
progress: 0,
356-
error: 'Something went wrong',
357-
selectFile: vi.fn(),
358-
uploadFile: vi.fn(),
359-
reset: resetMock,
360-
formatFileSize: vi.fn(),
361-
cancelUpload: vi.fn(),
362-
});
363-
364-
render(<UploadModal {...mockProps} />);
365-
366-
// Find and click the try again button
367-
const tryAgainButton = screen.getByText('common.tryAgain');
368-
fireEvent.click(tryAgainButton);
369-
370-
// Check that reset was called
371-
expect(resetMock).toHaveBeenCalled();
372-
});
373-
374350
test('renders nothing when isOpen is false', () => {
375351
// Mock the hook before rendering the component
376352
(useFileUpload as unknown as ReturnType<typeof vi.fn>).mockReturnValue({

frontend/src/common/services/ai/bedrock.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class BedrockService {
2323
constructor() {
2424
// Check if we're in a test environment (Node.js environment with no window)
2525
this.isTestEnvironment = typeof window === 'undefined' ||
26-
process.env.NODE_ENV === 'test' ||
27-
!!process.env.VITEST;
26+
import.meta.env.MODE === 'test' ||
27+
import.meta.env.VITEST === 'true';
2828

2929
// Only initialize the client in non-test environments or if explicitly required
3030
if (!this.isTestEnvironment) {

frontend/src/common/utils/i18n/resources/en/common.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
"error": {
4545
"noFile": "No file selected",
4646
"permissionDenied": "Permission denied",
47-
"unknown": "An unknown error occurred"
47+
"unknown": "An unknown error occurred",
48+
"fileTooBig": "File is too large. Please upload a smaller file.",
49+
"invalidType": "Invalid file type. Please upload a supported file type."
4850
}
4951
},
5052
"common": {

frontend/src/common/utils/i18n/resources/es/common.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
"error": {
4545
"noFile": "Ningún archivo seleccionado",
4646
"permissionDenied": "Permiso denegado",
47-
"unknown": "Se produjo un error desconocido"
47+
"unknown": "Se produjo un error desconocido",
48+
"fileTooBig": "El archivo es demasiado grande. Por favor, sube un archivo más pequeño.",
49+
"invalidType": "Tipo de archivo no válido. Por favor, sube un tipo de archivo compatible."
4850
}
4951
},
5052
"common": {

frontend/src/common/utils/i18n/resources/fr/common.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
"error": {
4545
"noFile": "Aucun fichier sélectionné",
4646
"permissionDenied": "Permission refusée",
47-
"unknown": "Une erreur inconnue s'est produite"
47+
"unknown": "Une erreur inconnue s'est produite",
48+
"fileTooBig": "Le fichier est trop volumineux. Veuillez télécharger un fichier plus petit.",
49+
"invalidType": "Type de fichier non valide. Veuillez télécharger un type de fichier pris en charge."
4850
}
4951
},
5052
"common": {

frontend/src/theme/variables.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,12 @@ http://ionicframework.com/docs/theming/ */
1515
--ion-color-primary-contrast-rgb: 255, 255, 255;
1616
--ion-color-primary-shade: #0049c7;
1717
--ion-color-primary-tint: #1a64e0;
18+
19+
--ion-color-danger: #FF9cb4;
20+
--ion-color-danger-rgb: 233, 0, 0;
21+
--ion-color-danger-contrast: #ffffff;
22+
--ion-color-danger-contrast-rgb: 255, 255, 255;
23+
--ion-color-danger-shade: #c70000;
24+
--ion-color-danger-tint: #e01a1a;
1825

1926
}

0 commit comments

Comments
 (0)